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

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

192 lines 13.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 const el = window.wp.element.createElement;
3 const { registerBlockType } = window.wp.blocks;
4 const { InspectorControls, useBlockProps, PanelColorSettings } = window.wp.blockEditor;
5 const { PanelBody, RangeControl, ToggleControl, TextControl, SelectControl, Button } = window.wp.components;
6 const { __ } = window.wp.i18n;
7
8 var _bkbgCSgetTC, _bkbgCSgetTV;
9 function getTC() { return _bkbgCSgetTC || (_bkbgCSgetTC = window.bkbgTypographyControl); }
10 function getTV() { return _bkbgCSgetTV || (_bkbgCSgetTV = window.bkbgTypoCssVars); }
11 function tv(obj, prefix) { var fn = getTV(); return fn ? fn(obj, prefix) : {}; }
12
13 // ── Helpers ───────────────────────────────────────────────────────────────
14 function overallScore( criteria ) {
15 if ( ! criteria || ! criteria.length ) return 0;
16 const total = criteria.reduce( ( s, c ) => s + ( ( c.score || 0 ) / ( c.max || 100 ) ) * 100, 0 );
17 return Math.round( total / criteria.length );
18 }
19
20 function scoreGrade( n ) {
21 if ( n >= 90 ) return 'A';
22 if ( n >= 80 ) return 'B';
23 if ( n >= 70 ) return 'C';
24 if ( n >= 60 ) return 'D';
25 return 'F';
26 }
27
28 function scoreColor( n, a ) {
29 if ( n >= 75 ) return a.passColor;
30 if ( n >= 50 ) return a.warnColor;
31 return a.failColor;
32 }
33
34 // ── Circular gauge ────────────────────────────────────────────────────────
35 function renderGauge( a, score ) {
36 const S = a.gaugeSize;
37 const SW = a.strokeWidth;
38 const r = ( S - SW * 2 ) / 2;
39 const cx = S / 2;
40 const cy = S / 2;
41 const circ = 2 * Math.PI * r;
42 // Arc from -90° (top), goes clockwise
43 const offset = circ - ( score / 100 ) * circ;
44 const clr = scoreColor( score, a );
45 const grade = a.showGrade ? scoreGrade( score ) : '';
46
47 return el( 'div', { className: 'bkbg-cs-gauge', style: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8, flexShrink: 0 } },
48 a.label && el( 'div', { className: 'bkbg-cs-gauge-label', style: { color: a.mutedColor, textAlign: 'center' } }, a.label ),
49 el( 'svg', { width: S, height: S, viewBox: `0 0 ${ S } ${ S }`, style: { display: 'block' } },
50 // Track
51 el( 'circle', { cx, cy, r, fill: 'none', stroke: a.trackColor, strokeWidth: SW } ),
52 // Progress arc
53 el( 'circle', {
54 cx, cy, r,
55 fill: 'none',
56 stroke: clr,
57 strokeWidth: SW,
58 strokeDasharray: circ,
59 strokeDashoffset: offset,
60 strokeLinecap: 'round',
61 transform: `rotate(-90 ${ cx } ${ cy })`,
62 style: { transition: 'stroke-dashoffset 0.5s' }
63 } ),
64 // Score number
65 el( 'text', { x: cx, y: cy - ( grade ? 10 : 0 ), textAnchor: 'middle', dominantBaseline: 'middle', fontSize: S * 0.22, fontWeight: 800, fill: clr }, score ),
66 // Grade
67 grade && el( 'text', { x: cx, y: cy + S * 0.14, textAnchor: 'middle', dominantBaseline: 'middle', fontSize: S * 0.12, fontWeight: 700, fill: a.mutedColor }, `Grade ${ grade }` ),
68 ),
69 );
70 }
71
72 // ── Criteria list ─────────────────────────────────────────────────────────
73 function renderCriteria( a ) {
74 const criteria = a.criteria || [];
75 return el( 'div', { className: 'bkbg-cs-criteria', style: { display: 'flex', flexDirection: 'column', gap: 10, flex: 1 } },
76 criteria.map( ( c, ci ) => {
77 const pct = Math.min( 100, Math.round( ( ( c.score || 0 ) / ( c.max || 100 ) ) * 100 ) );
78 const clr = scoreColor( pct, a );
79 return el( 'div', { key: ci, className: 'bkbg-cs-criterion' },
80 el( 'div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 } },
81 el( 'span', { className: 'bkbg-cs-crit-label', style: { color: a.textColor } }, c.label ),
82 el( 'span', { className: 'bkbg-cs-crit-score', style: { color: clr, minWidth: 36, textAlign: 'right' } }, `${ c.score }/${ c.max }` ),
83 ),
84 el( 'div', { style: { height: 6, background: a.trackColor, borderRadius: 99, overflow: 'hidden', marginBottom: c.note ? 3 : 0 } },
85 el( 'div', { style: { height: '100%', width: pct + '%', background: clr, borderRadius: 99 } } )
86 ),
87 c.note && el( 'span', { className: 'bkbg-cs-crit-note', style: { color: a.mutedColor } }, c.note ),
88 );
89 } )
90 );
91 }
92
93 // ── Combined render ───────────────────────────────────────────────────────
94 function renderScore( a ) {
95 const score = overallScore( a.criteria );
96 const isStack = a.layout !== 'side';
97 const containerStyle = {
98 display: 'flex',
99 flexDirection: isStack ? 'column' : 'row',
100 gap: 28,
101 alignItems: isStack ? 'center' : 'flex-start',
102 };
103 return el( 'div', { className: 'bkbg-cs-inner', style: containerStyle },
104 a.showGauge && renderGauge( a, score ),
105 a.showCriteria && renderCriteria( a ),
106 );
107 }
108
109 function updCriterion( setAttributes, criteria, ci, field, val ) {
110 setAttributes( { criteria: criteria.map( ( c, i ) => i === ci ? { ...c, [field]: val } : c ) } );
111 }
112
113 // ── Block ─────────────────────────────────────────────────────────────────
114 registerBlockType( 'blockenberg/content-score', {
115 edit: function ( props ) {
116 const { attributes: a, setAttributes } = props;
117 const blockProps = useBlockProps( { className: 'bkbg-cs-wrap', style: Object.assign({ background: a.bgColor, padding: '20px', borderRadius: a.borderRadius + 'px', boxSizing: 'border-box', '--bkcs-label-sz': (a.labelSize || 13) + 'px' }, tv(a.typoTitle, '--bkcs-title-'), tv(a.typoBody, '--bkcs-body-')) } );
118
119 return el( 'div', blockProps,
120 el( InspectorControls, {},
121
122 el( PanelBody, { title: __( 'Settings', 'blockenberg' ), initialOpen: true },
123 el( TextControl, { label: __( 'Title', 'blockenberg' ), value: a.title, onChange: v => setAttributes( { title: v } ) } ),
124 el( ToggleControl, { label: __( 'Show Title', 'blockenberg' ), checked: a.showTitle, onChange: v => setAttributes( { showTitle: v } ) } ),
125 el( TextControl, { label: __( 'Gauge Label', 'blockenberg' ), value: a.label, onChange: v => setAttributes( { label: v } ) } ),
126 el( ToggleControl, { label: __( 'Show Gauge', 'blockenberg' ), checked: a.showGauge, onChange: v => setAttributes( { showGauge: v } ) } ),
127 el( ToggleControl, { label: __( 'Show Grade', 'blockenberg' ), checked: a.showGrade, onChange: v => setAttributes( { showGrade: v } ) } ),
128 el( ToggleControl, { label: __( 'Show Criteria', 'blockenberg' ), checked: a.showCriteria, onChange: v => setAttributes( { showCriteria: v } ) } ),
129 el( SelectControl, {
130 label: __( 'Layout', 'blockenberg' ),
131 value: a.layout,
132 options: [ { label: __( 'Side by Side', 'blockenberg' ), value: 'side' }, { label: __( 'Stacked', 'blockenberg' ), value: 'stack' } ],
133 onChange: v => setAttributes( { layout: v } ),
134 } ),
135 ),
136
137 el( PanelBody, { title: __( 'Style', 'blockenberg' ), initialOpen: false },
138 el( RangeControl, { label: __( 'Gauge Size', 'blockenberg' ), value: a.gaugeSize, onChange: v => setAttributes( { gaugeSize: v } ), min: 80, max: 260, step: 10 } ),
139 el( RangeControl, { label: __( 'Ring Width', 'blockenberg' ), value: a.strokeWidth, onChange: v => setAttributes( { strokeWidth: v } ), min: 5, max: 30 } ),
140 el( RangeControl, { label: __( 'Border Radius', 'blockenberg' ),value: a.borderRadius,onChange: v => setAttributes( { borderRadius:v } ), min: 0, max: 28 } ),
141 ),
142
143
144 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
145 getTC() && el( getTC(), { label: __( 'Title', 'blockenberg' ), value: a.typoTitle, onChange: function (v) { setAttributes({ typoTitle: v }); } }),
146 getTC() && el( getTC(), { label: __( 'Body Text', 'blockenberg' ), value: a.typoBody, onChange: function (v) { setAttributes({ typoBody: v }); } }),
147 el(RangeControl, { __nextHasNoMarginBottom: true, label: __('Label Size (px)', 'blockenberg'), value: a.labelSize, min: 10, max: 24, onChange: function (v) { setAttributes({ labelSize: v }); } })
148 ),
149 el( PanelColorSettings, {
150 title: __( 'Colors', 'blockenberg' ), initialOpen: false,
151 colorSettings: [
152 { label: __( 'Background', 'blockenberg' ), value: a.bgColor, onChange: v => setAttributes( { bgColor: v || '#ffffff' } ) },
153 { label: __( 'Card BG', 'blockenberg' ), value: a.cardBg, onChange: v => setAttributes( { cardBg: v || '#f9fafb' } ) },
154 { label: __( 'Title', 'blockenberg' ), value: a.titleColor, onChange: v => setAttributes( { titleColor: v || '#111827' } ) },
155 { label: __( 'Text', 'blockenberg' ), value: a.textColor, onChange: v => setAttributes( { textColor: v || '#374151' } ) },
156 { label: __( 'Muted', 'blockenberg' ), value: a.mutedColor, onChange: v => setAttributes( { mutedColor: v || '#6b7280' } ) },
157 { label: __( 'Track', 'blockenberg' ), value: a.trackColor, onChange: v => setAttributes( { trackColor: v || '#e5e7eb' } ) },
158 { label: __( 'Pass (≥75%)', 'blockenberg' ),value: a.passColor, onChange: v => setAttributes( { passColor: v || '#10b981' } ) },
159 { label: __( 'Warn (50–74%)', 'blockenberg' ),value: a.warnColor,onChange: v => setAttributes( { warnColor: v || '#f59e0b' } ) },
160 { label: __( 'Fail (<50%)', 'blockenberg' ), value: a.failColor, onChange: v => setAttributes( { failColor: v || '#ef4444' } ) },
161 ]
162 } ),
163
164 el( PanelBody, { title: __( 'Criteria', 'blockenberg' ), initialOpen: false },
165 el( Button, { variant: 'secondary', style: { marginBottom: 10 }, onClick: () => setAttributes( { criteria: [ ...( a.criteria || [] ), { label: 'New Criterion', score: 70, max: 100, note: '' } ] } ) }, __( '+ Add Criterion', 'blockenberg' ) ),
166 a.criteria.map( ( c, ci ) =>
167 el( PanelBody, { key: ci, title: `${ c.label } (${ c.score }/${ c.max })`, initialOpen: false },
168 el( TextControl, { label: __( 'Label', 'blockenberg' ), value: c.label, onChange: v => updCriterion( setAttributes, a.criteria, ci, 'label', v ) } ),
169 el( RangeControl, { label: __( 'Score', 'blockenberg' ), value: c.score, onChange: v => updCriterion( setAttributes, a.criteria, ci, 'score', v ), min: 0, max: c.max || 100 } ),
170 el( RangeControl, { label: __( 'Max', 'blockenberg' ), value: c.max, onChange: v => updCriterion( setAttributes, a.criteria, ci, 'max', v ), min: 1, max: 200 } ),
171 el( TextControl, { label: __( 'Note', 'blockenberg' ), value: c.note, onChange: v => updCriterion( setAttributes, a.criteria, ci, 'note', v ) } ),
172 el( Button, { isDestructive: true, isSmall: true, onClick: () => setAttributes( { criteria: a.criteria.filter( ( _, x ) => x !== ci ) } ) }, __( 'Remove', 'blockenberg' ) ),
173 )
174 ),
175 ),
176 ),
177
178 a.showTitle && a.title && el( 'h3', { className: 'bkbg-cs-title', style: { color: a.titleColor } }, a.title ),
179 renderScore( a ),
180 );
181 },
182
183 save: function ( { attributes: a } ) {
184 const blockProps = useBlockProps.save( { className: 'bkbg-cs-wrap', style: Object.assign({ background: a.bgColor, padding: '20px', borderRadius: a.borderRadius + 'px', boxSizing: 'border-box', '--bkcs-label-sz': (a.labelSize || 13) + 'px' }, tv(a.typoTitle, '--bkcs-title-'), tv(a.typoBody, '--bkcs-body-')) } );
185 return el( 'div', blockProps,
186 a.showTitle && a.title ? el( 'h3', { className: 'bkbg-cs-title', style: { color: a.titleColor } }, a.title ) : null,
187 renderScore( a ),
188 );
189 },
190 } );
191 }() );
192