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

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

494 lines 26.4 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 useBlockProps = wp.blockEditor.useBlockProps;
9 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
10 var PanelBody = wp.components.PanelBody;
11 var ToggleControl = wp.components.ToggleControl;
12 var RangeControl = wp.components.RangeControl;
13 var SelectControl = wp.components.SelectControl;
14 var TextControl = wp.components.TextControl;
15 var Button = wp.components.Button;
16
17 /* ── updateItem helper (ES5 safe) ── */
18 function updateItem(arr, idx, field, val) {
19 return arr.map(function (item, i) {
20 if (i !== idx) return item;
21 var p = {}; p[field] = val;
22 return Object.assign({}, item, p);
23 });
24 }
25
26 /* ── util ── */
27 function timestamp() {
28 var now = new Date();
29 var h = now.getHours();
30 var m = now.getMinutes();
31 var ampm = h >= 12 ? 'PM' : 'AM';
32 h = h % 12 || 12;
33 return h + ':' + (m < 10 ? '0' : '') + m + ' ' + ampm;
34 }
35
36 var _fchtTC, _fchtTV;
37 function _tc() { return _fchtTC || (_fchtTC = window.bkbgTypographyControl); }
38 function _tv(o, p) { if (!_fchtTV) _fchtTV = window.bkbgTypoCssVars; return _fchtTV ? _fchtTV(o, p) : {}; }
39
40 /* ── editor preview component ── */
41 function ChatPreview(props) {
42 var a = props.attributes;
43 var ts = timestamp();
44 var isRounded = a.chatStyle !== 'sharp';
45 var bubbleR = a.chatStyle === 'bubble' ? '20px' : (isRounded ? '12px' : '4px');
46 var botBubbleStyle = {
47 background: a.botBubbleBg,
48 color: a.botBubbleColor,
49 borderRadius: bubbleR,
50 padding: '10px 14px',
51 display: 'inline-block',
52 maxWidth: '78%',
53 boxShadow: '0 1px 4px rgba(0,0,0,0.08)'
54 };
55 var userBubbleStyle = {
56 background: a.userBubbleBg,
57 color: a.userBubbleColor,
58 borderRadius: bubbleR,
59 padding: '10px 14px',
60 display: 'inline-block',
61 maxWidth: '78%'
62 };
63
64 return el('div', {
65 style: {
66 background: a.chatBg,
67 borderRadius: (a.borderRadius - 2) + 'px',
68 padding: '16px',
69 minHeight: '200px',
70 fontFamily: 'system-ui, sans-serif',
71 overflowY: 'auto'
72 }
73 },
74 /* initial bot message */
75 el('div', { style: { display: 'flex', alignItems: 'flex-end', gap: '8px', marginBottom: '14px' } },
76 el('div', {
77 style: {
78 width: '32px', height: '32px', borderRadius: '50%', flexShrink: 0,
79 background: a.avatarBg, display: 'flex', alignItems: 'center',
80 justifyContent: 'center', fontSize: '16px', lineHeight: 1
81 }
82 }, a.botAvatar),
83 el('div', {},
84 el('div', { style: { fontSize: '11px', color: a.timestampColor, marginBottom: '3px', fontWeight: 600 } }, a.botName),
85 el('div', { className: 'bkbg-faqc-bubble bot-bubble', style: botBubbleStyle }, a.initialMessage || 'Hi! Click any question below.')
86 )
87 ),
88
89 /* example user question */
90 el('div', { style: { display: 'flex', justifyContent: 'flex-end', marginBottom: '8px' } },
91 el('div', { className: 'bkbg-faqc-bubble user-bubble', style: userBubbleStyle }, a.faqs.length ? a.faqs[0].question : 'Sample question?')
92 ),
93
94 /* typing indicator */
95 el('div', { style: { display: 'flex', alignItems: 'flex-end', gap: '8px', marginBottom: '14px' } },
96 el('div', {
97 style: {
98 width: '32px', height: '32px', borderRadius: '50%', flexShrink: 0,
99 background: a.avatarBg, display: 'flex', alignItems: 'center',
100 justifyContent: 'center', fontSize: '16px', lineHeight: 1
101 }
102 }, a.botAvatar),
103 el('div', {},
104 el('div', { style: { fontSize: '11px', color: a.timestampColor, marginBottom: '3px', fontWeight: 600 } }, a.botName),
105 el('div', { className: 'bkbg-faqc-bubble bot-bubble', style: Object.assign({}, botBubbleStyle, { fontStyle: 'italic', color: a.timestampColor }) }, '...')
106 )
107 ),
108
109 /* example answer */
110 el('div', { style: { display: 'flex', alignItems: 'flex-end', gap: '8px' } },
111 el('div', {
112 style: {
113 width: '32px', height: '32px', borderRadius: '50%', flexShrink: 0,
114 background: a.avatarBg, display: 'flex', alignItems: 'center',
115 justifyContent: 'center', fontSize: '16px', lineHeight: 1
116 }
117 }, a.botAvatar),
118 el('div', {},
119 el('div', { style: { fontSize: '11px', color: a.timestampColor, marginBottom: '3px', fontWeight: 600 } },
120 a.botName + (a.showTimestamp ? ' ' + ts : '')
121 ),
122 el('div', { className: 'bkbg-faqc-bubble bot-bubble', style: botBubbleStyle },
123 a.faqs.length ? a.faqs[0].answer.substring(0, 80) + (a.faqs[0].answer.length > 80 ? '' : '') : 'Answer preview.'
124 )
125 )
126 )
127 );
128 }
129
130 /* ── FAQ list editor ── */
131 function FaqEditor(props) {
132 var faqs = props.faqs;
133 var setFaqs = props.setFaqs;
134 var expanded = props.expanded;
135 var setExpanded = props.setExpanded;
136
137 return el('div', {},
138 faqs.map(function (faq, i) {
139 var isOpen = expanded === i;
140 return el('div', {
141 key: i,
142 style: {
143 border: '1px solid #e5e7eb',
144 borderRadius: '6px',
145 marginBottom: '6px',
146 overflow: 'hidden',
147 background: isOpen ? '#fafafa' : '#fff'
148 }
149 },
150 /* header row */
151 el('div', {
152 onClick: function () { setExpanded(isOpen ? -1 : i); },
153 style: {
154 padding: '8px 10px', cursor: 'pointer', display: 'flex',
155 alignItems: 'center', gap: '6px',
156 borderBottom: isOpen ? '1px solid #e5e7eb' : 'none'
157 }
158 },
159 el('span', { style: { fontSize: '10px', color: '#9ca3af', fontWeight: 700 } }, 'Q' + (i + 1)),
160 el('span', {
161 style: {
162 flex: 1, fontSize: '12px', color: '#374151',
163 overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'
164 }
165 }, faq.question || '(empty)'),
166 el('span', { style: { color: '#9ca3af', fontSize: '11px' } }, isOpen ? '' : ''),
167 el('button', {
168 onClick: function (e) {
169 e.stopPropagation();
170 if (faqs.length < 2) return;
171 var next = faqs.filter(function (_, j) { return j !== i; });
172 setFaqs(next);
173 setExpanded(-1);
174 },
175 title: 'Remove',
176 style: {
177 background: 'none', border: 'none', cursor: 'pointer',
178 color: '#ef4444', fontSize: '14px', padding: '0 2px', lineHeight: 1
179 }
180 }, '×')
181 ),
182
183 /* expanded body */
184 isOpen ? el('div', { style: { padding: '10px' } },
185 el(TextControl, {
186 __nextHasNoMarginBottom: true,
187 label: __('Question', 'blockenberg'),
188 value: faq.question,
189 onChange: function (v) { setFaqs(updateItem(faqs, i, 'question', v)); }
190 }),
191 el('div', { style: { marginTop: '10px' } },
192 el('label', { style: { display: 'block', marginBottom: '4px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#757575' } },
193 __('Answer', 'blockenberg')
194 ),
195 el('textarea', {
196 value: faq.answer,
197 rows: 4,
198 style: { width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd', fontSize: '13px', resize: 'vertical', boxSizing: 'border-box' },
199 onChange: function (e) { setFaqs(updateItem(faqs, i, 'answer', e.target.value)); }
200 })
201 ),
202 el('div', { style: { marginTop: '8px', display: 'flex', gap: '6px' } },
203 el(Button, {
204 variant: 'secondary',
205 style: { fontSize: '11px' },
206 onClick: function () {
207 var copy = Object.assign({}, faq, { question: faq.question + ' (copy)' });
208 var next = faqs.slice(); next.splice(i + 1, 0, copy);
209 setFaqs(next);
210 setExpanded(i + 1);
211 }
212 }, __('Duplicate', 'blockenberg')),
213 i > 0 ? el(Button, {
214 variant: 'secondary',
215 style: { fontSize: '11px' },
216 onClick: function () {
217 var next = faqs.slice();
218 var tmp = next[i - 1]; next[i - 1] = next[i]; next[i] = tmp;
219 setFaqs(next); setExpanded(i - 1);
220 }
221 }, __('↑ Move up', 'blockenberg')) : null,
222 i < faqs.length - 1 ? el(Button, {
223 variant: 'secondary',
224 style: { fontSize: '11px' },
225 onClick: function () {
226 var next = faqs.slice();
227 var tmp = next[i + 1]; next[i + 1] = next[i]; next[i] = tmp;
228 setFaqs(next); setExpanded(i + 1);
229 }
230 }, __('↓ Move down', 'blockenberg')) : null
231 )
232 ) : null
233 );
234 }),
235
236 /* add button */
237 el('div', { style: { marginTop: '8px' } },
238 el(Button, {
239 variant: 'primary',
240 style: { width: '100%', justifyContent: 'center' },
241 onClick: function () {
242 var next = faqs.slice();
243 next.push({ question: 'New question?', answer: 'Your answer goes here.' });
244 setFaqs(next);
245 setExpanded(next.length - 1);
246 }
247 }, __('+ Add FAQ', 'blockenberg'))
248 )
249 );
250 }
251
252 /* ── register ── */
253 registerBlockType('blockenberg/faq-chat', {
254 edit: function (props) {
255 var attributes = props.attributes;
256 var setAttributes = props.setAttributes;
257 var a = attributes;
258
259 var expanded = useState(-1);
260 var expandedIdx = expanded[0];
261 var setExpanded = expanded[1];
262
263 var blockProps = useBlockProps({ className: 'bkbg-faqc-editor-wrap', style: Object.assign({}, _tv(a.typoQuestion, '--bkbg-fcht-qt-'), _tv(a.typoAnswer, '--bkbg-fcht-an-')) });
264
265 function setFaqs(v) { setAttributes({ faqs: v }); }
266
267 return el(Fragment, {},
268
269 el(InspectorControls, {},
270
271 /* ── FAQ Items ── */
272 el(PanelBody, { title: __('FAQ Items', 'blockenberg'), initialOpen: true },
273 el(FaqEditor, {
274 faqs: a.faqs,
275 setFaqs: setFaqs,
276 expanded: expandedIdx,
277 setExpanded: setExpanded
278 })
279 ),
280
281 /* ── Bot Settings ── */
282 el(PanelBody, { title: __('Bot & Greeting', 'blockenberg'), initialOpen: false },
283 el(TextControl, {
284 __nextHasNoMarginBottom: true,
285 label: __('Bot name', 'blockenberg'),
286 value: a.botName,
287 onChange: function (v) { setAttributes({ botName: v }); }
288 }),
289 el('div', { style: { marginTop: '10px' } },
290 el(TextControl, {
291 __nextHasNoMarginBottom: true,
292 label: __('Bot avatar (emoji or initials)', 'blockenberg'),
293 value: a.botAvatar,
294 onChange: function (v) { setAttributes({ botAvatar: v.slice(0, 4) }); }
295 })
296 ),
297 el('div', { style: { marginTop: '10px' } },
298 el('label', { style: { display: 'block', marginBottom: '4px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#757575' } },
299 __('Initial greeting message', 'blockenberg')
300 ),
301 el('textarea', {
302 value: a.initialMessage,
303 rows: 2,
304 style: { width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ddd', fontSize: '13px', resize: 'vertical', boxSizing: 'border-box' },
305 onChange: function (e) { setAttributes({ initialMessage: e.target.value }); }
306 })
307 ),
308 el('div', { style: { marginTop: '10px' } },
309 el(ToggleControl, {
310 __nextHasNoMarginBottom: true,
311 label: __('Show message timestamps', 'blockenberg'),
312 checked: a.showTimestamp,
313 onChange: function (v) { setAttributes({ showTimestamp: v }); }
314 })
315 ),
316 el('div', { style: { marginTop: '10px' } },
317 el(RangeControl, {
318 __nextHasNoMarginBottom: true,
319 label: __('Typing delay (ms)', 'blockenberg'),
320 value: a.typingDelay,
321 min: 200, max: 2500, step: 100,
322 onChange: function (v) { setAttributes({ typingDelay: v }); }
323 })
324 )
325 ),
326
327 /* ── Appearance ── */
328 el(PanelBody, { title: __('Appearance', 'blockenberg'), initialOpen: false },
329 el(SelectControl, {
330 __nextHasNoMarginBottom: true,
331 label: __('Chat style', 'blockenberg'),
332 value: a.chatStyle,
333 options: [
334 { label: 'Rounded', value: 'rounded' },
335 { label: 'Bubble', value: 'bubble' },
336 { label: 'Sharp', value: 'sharp' }
337 ],
338 onChange: function (v) { setAttributes({ chatStyle: v }); }
339 }),
340 el('div', { style: { marginTop: '12px' } },
341 el(RangeControl, {
342 __nextHasNoMarginBottom: true,
343 label: __('Max width (px)', 'blockenberg'),
344 value: a.maxWidth,
345 min: 300, max: 900,
346 onChange: function (v) { setAttributes({ maxWidth: v }); }
347 })
348 ),
349 el('div', { style: { marginTop: '12px' } },
350 el(RangeControl, {
351 __nextHasNoMarginBottom: true,
352 label: __('Chat area max height (px)', 'blockenberg'),
353 value: a.maxHeight,
354 min: 200, max: 800,
355 onChange: function (v) { setAttributes({ maxHeight: v }); }
356 })
357 ),
358 el('div', { style: { marginTop: '12px' } },
359 el(RangeControl, {
360 __nextHasNoMarginBottom: true,
361 label: __('Border radius (px)', 'blockenberg'),
362 value: a.borderRadius,
363 min: 0, max: 32,
364 onChange: function (v) { setAttributes({ borderRadius: v }); }
365 })
366 ),
367 ),
368
369 /* ── Colors ── */
370
371 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
372 _tc() && _tc()({ label: __('Question', 'blockenberg'), typo: a.typoQuestion, onChange: function (v) { setAttributes({ typoQuestion: v }); } }),
373 _tc() && _tc()({ label: __('Answer', 'blockenberg'), typo: a.typoAnswer, onChange: function (v) { setAttributes({ typoAnswer: v }); } })
374 ),
375 el(PanelColorSettings, {
376 title: __('Colors', 'blockenberg'),
377 initialOpen: false,
378 colorSettings: [
379 { label: __('Header background', 'blockenberg'), value: a.headerBg, onChange: function (v) { setAttributes({ headerBg: v || '#6366f1' }); } },
380 { label: __('Header text', 'blockenberg'), value: a.headerColor, onChange: function (v) { setAttributes({ headerColor: v || '#ffffff' }); } },
381 { label: __('Avatar background', 'blockenberg'), value: a.avatarBg, onChange: function (v) { setAttributes({ avatarBg: v || '#6366f1' }); } },
382 { label: __('Chat area background', 'blockenberg'), value: a.chatBg, onChange: function (v) { setAttributes({ chatBg: v || '#f1f5f9' }); } },
383 { label: __('Bot bubble background', 'blockenberg'), value: a.botBubbleBg, onChange: function (v) { setAttributes({ botBubbleBg: v || '#ffffff' }); } },
384 { label: __('Bot bubble text', 'blockenberg'), value: a.botBubbleColor, onChange: function (v) { setAttributes({ botBubbleColor: v || '#1f2937' }); } },
385 { label: __('User bubble background', 'blockenberg'), value: a.userBubbleBg, onChange: function (v) { setAttributes({ userBubbleBg: v || '#6366f1' }); } },
386 { label: __('User bubble text', 'blockenberg'), value: a.userBubbleColor, onChange: function (v) { setAttributes({ userBubbleColor: v || '#ffffff' }); } },
387 { label: __('Questions area background', 'blockenberg'), value: a.questionsBg, onChange: function (v) { setAttributes({ questionsBg: v || '#ffffff' }); } },
388 { label: __('Question button background', 'blockenberg'), value: a.btnBg, onChange: function (v) { setAttributes({ btnBg: v || '#f3f4f6' }); } },
389 { label: __('Question button text', 'blockenberg'), value: a.btnColor, onChange: function (v) { setAttributes({ btnColor: v || '#374151' }); } },
390 { label: __('Question button border', 'blockenberg'), value: a.btnBorder, onChange: function (v) { setAttributes({ btnBorder: v || '#e5e7eb' }); } },
391 { label: __('Answered button background', 'blockenberg'), value: a.btnActiveBg, onChange: function (v) { setAttributes({ btnActiveBg: v || '#e0e7ff' }); } },
392 { label: __('Answered button text', 'blockenberg'), value: a.btnActiveColor, onChange: function (v) { setAttributes({ btnActiveColor: v || '#4338ca' }); } },
393 { label: __('Widget border', 'blockenberg'), value: a.borderColor, onChange: function (v) { setAttributes({ borderColor: v || '#e5e7eb' }); } },
394 { label: __('Timestamp color', 'blockenberg'), value: a.timestampColor, onChange: function (v) { setAttributes({ timestampColor: v || '#9ca3af' }); } }
395 ]
396 })
397 ),
398
399 /* ────── canvas ────── */
400 el('div', blockProps,
401 el('div', {
402 style: {
403 fontFamily: 'system-ui, -apple-system, sans-serif',
404 maxWidth: a.maxWidth + 'px',
405 margin: '0 auto',
406 border: '1px solid ' + a.borderColor,
407 borderRadius: a.borderRadius + 'px',
408 overflow: 'hidden',
409 boxShadow: '0 4px 24px rgba(0,0,0,0.07)'
410 }
411 },
412 /* header */
413 el('div', {
414 style: {
415 background: a.headerBg,
416 padding: '14px 18px',
417 display: 'flex',
418 alignItems: 'center',
419 gap: '10px'
420 }
421 },
422 el('div', {
423 style: {
424 width: '36px', height: '36px', borderRadius: '50%',
425 background: 'rgba(255,255,255,0.2)',
426 display: 'flex', alignItems: 'center', justifyContent: 'center',
427 fontSize: '18px', flexShrink: 0
428 }
429 }, a.botAvatar),
430 el('div', {},
431 el('div', { style: { color: a.headerColor, fontWeight: 700, fontSize: '15px', lineHeight: 1.2 } }, a.botName),
432 el('div', { style: { color: a.headerColor, opacity: 0.75, fontSize: '12px' } },
433 el('span', { style: { display: 'inline-block', width: '7px', height: '7px', borderRadius: '50%', background: '#4ade80', marginRight: '4px', verticalAlign: 'middle' } }),
434 'Online · ' + a.faqs.length + ' topics'
435 )
436 )
437 ),
438
439 /* chat preview */
440 el('div', { style: { maxHeight: a.maxHeight * 0.55 + 'px', overflowY: 'auto' } },
441 el(ChatPreview, { attributes: a })
442 ),
443
444 /* question buttons */
445 el('div', {
446 style: {
447 background: a.questionsBg,
448 borderTop: '1px solid ' + a.borderColor,
449 padding: '12px 14px'
450 }
451 },
452 el('div', { style: { fontSize: '11px', color: a.timestampColor, marginBottom: '8px', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em' } },
453 '📋 Frequently Asked Questions'
454 ),
455 el('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '6px' } },
456 a.faqs.slice(0, 4).map(function (faq, i) {
457 return el('button', {
458 key: i,
459 className: 'bkbg-faqc-q-btn',
460 style: {
461 background: a.btnBg, color: a.btnColor,
462 borderColor: a.btnBorder,
463 cursor: 'pointer',
464 transition: 'all 0.15s'
465 }
466 }, faq.question);
467 }),
468 a.faqs.length > 4 ? el('button', {
469 style: {
470 background: 'transparent', color: a.timestampColor,
471 border: '1px dashed ' + a.btnBorder,
472 borderRadius: '20px', padding: '6px 12px',
473 fontSize: '13px', cursor: 'default',
474 fontFamily: 'inherit', lineHeight: 1.4
475 }
476 }, '+' + (a.faqs.length - 4) + ' more') : null
477 )
478 )
479 )
480 )
481 );
482 },
483
484 save: function (props) {
485 return el('div', useBlockProps.save(),
486 el('div', {
487 className: 'bkbg-faqc-app',
488 'data-opts': JSON.stringify(props.attributes)
489 })
490 );
491 }
492 });
493 }() );
494