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

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

385 lines 22.1 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 __ = wp.i18n.__;
4 var useBlockProps = wp.blockEditor.useBlockProps;
5 var InspectorControls = wp.blockEditor.InspectorControls;
6 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
7 var PanelBody = wp.components.PanelBody;
8 var TextControl = wp.components.TextControl;
9 var TextareaControl = wp.components.TextareaControl;
10 var ToggleControl = wp.components.ToggleControl;
11 var SelectControl = wp.components.SelectControl;
12 var RangeControl = wp.components.RangeControl;
13 var useRef = wp.element.useRef;
14 var useEffect = wp.element.useEffect;
15
16 function getTypographyControl() { return (window.bkbgTypographyControl || function () { return null; }); }
17 function _tv() { var fn = window.bkbgTypoCssVars; return fn ? fn.apply(null, arguments) : {}; }
18
19 var LANGS = [
20 { label: 'JavaScript', value: 'javascript' },
21 { label: 'TypeScript', value: 'typescript' },
22 { label: 'PHP', value: 'php' },
23 { label: 'Python', value: 'python' },
24 { label: 'CSS', value: 'css' },
25 { label: 'HTML', value: 'html' },
26 { label: 'Bash / Shell', value: 'bash' },
27 { label: 'SQL', value: 'sql' },
28 { label: 'JSON', value: 'json' },
29 { label: 'Ruby', value: 'ruby' },
30 { label: 'Go', value: 'go' },
31 { label: 'Rust', value: 'rust' },
32 { label: 'Plain Text', value: 'text' }
33 ];
34
35 var THEMES = [
36 { label: 'Dark (Catppuccin)', value: 'dark' },
37 { label: 'Light', value: 'light' },
38 { label: 'Dracula', value: 'dracula' },
39 { label: 'GitHub', value: 'github' },
40 { label: 'Custom', value: 'custom' }
41 ];
42
43 var THEME_PRESETS = {
44 dark: { bgL: '#1e1e2e', bgR: '#1e2e1e', hdL: '#ff6b6b', hdR: '#6bcb77', hdColor: '#fff', code: '#cdd6f4', lineNum: '#6c7086', addBg: 'rgba(107,203,119,0.15)', remBg: 'rgba(255,107,107,0.15)' },
45 light: { bgL: '#fdf6e3', bgR: '#f0fff0', hdL: '#e06c75', hdR: '#27ae60', hdColor: '#fff', code: '#24292e', lineNum: '#999', addBg: 'rgba(39,174,96,0.12)', remBg: 'rgba(224,108,117,0.12)' },
46 dracula: { bgL: '#282a36', bgR: '#1a3028', hdL: '#ff5555', hdR: '#50fa7b', hdColor: '#f8f8f2', code: '#f8f8f2', lineNum: '#6272a4', addBg: 'rgba(80,250,123,0.12)', remBg: 'rgba(255,85,85,0.12)' },
47 github: { bgL: '#fff', bgR: '#f1fff1', hdL: '#ffd7d5', hdR: '#ccffd8', hdColor: '#1f2328', code: '#1f2328', lineNum: '#aaa', addBg: 'rgba(24,128,41,0.12)', remBg: 'rgba(164,14,38,0.12)' }
48 };
49
50 // Minimal tokeniser — returns array of {type, text}
51 function tokenize(code, lang) {
52 if (lang === 'text') return [{ type: 'plain', text: code }];
53
54 var keywords = {
55 javascript: 'break case catch class const continue debugger default delete do else enum export extends false finally for from function if import in instanceof let new null of return static super switch this throw true try typeof undefined var void while with yield async await',
56 typescript: 'break case catch class const continue debugger default delete do else enum export extends false finally for from function if import in instanceof interface let namespace new null of return static super switch this throw true try type typeof undefined var void while with yield async await',
57 php: 'echo print class function return if else elseif for foreach while do switch case break continue true false null new public private protected static abstract final try catch finally throw namespace use extends implements interface trait',
58 python: 'False None True and as assert async await break class continue def del elif else except finally for from global if import in is lambda nonlocal not or pass raise return try while with yield',
59 css: 'important',
60 bash: 'if then else elif fi for while do done case esac in function return export readonly local true false',
61 sql: 'SELECT FROM WHERE JOIN LEFT RIGHT INNER OUTER ON UPDATE INSERT INTO VALUES DELETE CREATE DROP TABLE ALTER ADD COLUMN AS GROUP BY ORDER HAVING LIMIT DISTINCT WITH UNION ALL SET AND OR NOT NULL IS IN EXISTS',
62 ruby: 'BEGIN END __ENCODING__ __END__ __FILE__ __LINE__ alias and begin break case class def defined do else elsif end ensure false for if in module next nil not or raise redo rescue retry return self super then true undef unless until when while yield',
63 go: 'break case chan const continue default defer else fallthrough for func go goto if import interface map package range return select struct switch type var nil true false',
64 rust: 'as async await break const continue crate dyn else enum extern false fn for if impl in let loop match mod move mut pub ref return self Self static struct super trait true type unsafe use where while'
65 };
66 var kw = (keywords[lang] || '').split(' ').filter(Boolean);
67
68 var rules = [
69 { type: 'string', re: /("""[\s\S]*?"""|'''[\s\S]*?'''|`[\s\S]*?`|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/ },
70 { type: 'comment', re: /(\/\/[^\n]*|\/\*[\s\S]*?\*\/|#[^\n]*|--[^\n]*)/ },
71 { type: 'number', re: /(\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)/ },
72 { type: 'operator', re: /([+\-*/%=<>!&|^~?:;,.()\[\]{}])/ },
73 { type: 'keyword', re: new RegExp('\\b(' + (kw.length ? kw.join('|') : '__NONE__') + ')\\b') },
74 { type: 'function', re: /\b([a-zA-Z_$][a-zA-Z0-9_$]*)(?=\s*\()/ },
75 { type: 'tag', re: lang === 'html' ? /(<\/?\w[^>]*>)/ : null },
76 { type: 'variable', re: lang === 'php' ? /(\$[a-zA-Z_]\w*)/ : null }
77 ].filter(function (r) { return r.re !== null; });
78
79 var tokens = [];
80 var rest = code;
81 while (rest.length) {
82 var best = null, bestIdx = Infinity, bestLen = 0, bestType = 'plain';
83 for (var i = 0; i < rules.length; i++) {
84 var m = rules[i].re.exec(rest);
85 if (m && m.index < bestIdx) {
86 best = m; bestIdx = m.index; bestLen = m[0].length; bestType = rules[i].type;
87 }
88 }
89 if (!best) { tokens.push({ type: 'plain', text: rest }); break; }
90 if (bestIdx > 0) tokens.push({ type: 'plain', text: rest.slice(0, bestIdx) });
91 tokens.push({ type: bestType, text: rest.slice(bestIdx, bestIdx + bestLen) });
92 rest = rest.slice(bestIdx + bestLen);
93 }
94 return tokens;
95 }
96
97 var TOKEN_COLORS = {
98 plain: null,
99 keyword: '#c792ea',
100 string: '#c3e88d',
101 comment: '#546e7a',
102 number: '#f78c6c',
103 operator: '#89ddff',
104 function: '#82aaff',
105 tag: '#f07178',
106 variable: '#ffcb6b'
107 };
108
109 function renderCode(code, lang, showLineNums, wrapLines, codeColor, lineNumColor, maxH, diffLinesArr, side) {
110 var lines = code.split('\n');
111 var rows = lines.map(function (line, i) {
112 var lineNum = i + 1;
113 var tokens = tokenize(line, lang);
114 var spans = tokens.map(function (t) {
115 var style = TOKEN_COLORS[t.type] ? { color: TOKEN_COLORS[t.type] } : { color: codeColor };
116 return el('span', { style: style }, t.text);
117 });
118 var isDiff = diffLinesArr.indexOf(i) !== -1;
119 var marker = '';
120 if (isDiff && side === 'left') marker = el('span', { style: { color: '#ff6b6b', userSelect: 'none', marginRight: '6px' } }, '');
121 if (isDiff && side === 'right') marker = el('span', { style: { color: '#6bcb77', userSelect: 'none', marginRight: '6px' } }, '+');
122 var diffBg = '';
123 if (isDiff && side === 'left') diffBg = 'rgba(255,107,107,0.13)';
124 if (isDiff && side === 'right') diffBg = 'rgba(107,203,119,0.13)';
125 return el('div', {
126 key: i,
127 style: {
128 display: 'flex', alignItems: 'flex-start', backgroundColor: diffBg || undefined,
129 whiteSpace: wrapLines ? 'pre-wrap' : 'pre', wordBreak: wrapLines ? 'break-word' : undefined,
130 minHeight: '1.5em'
131 }
132 },
133 showLineNums && el('span', {
134 style: { minWidth: '2.6em', textAlign: 'right', paddingRight: '1em', userSelect: 'none', color: lineNumColor, flexShrink: 0, fontSize: '0.85em', paddingTop: '1px' }
135 }, lineNum),
136 marker,
137 el('span', { style: { flex: 1, color: codeColor } }, ...spans)
138 );
139 });
140 return el('div', {
141 style: {
142 overflowY: 'auto', overflowX: maxH ? 'auto' : undefined,
143 maxHeight: maxH ? maxH + 'px' : undefined,
144 padding: '14px 16px',
145 fontSize: 'inherit', lineHeight: '1.6'
146 }
147 }, ...rows);
148 }
149
150 // ── Preview component ──────────────────────────────────────────────────────
151 function ComparisonPreview(props) {
152 var attr = props.attr;
153 var splitL = attr.splitRatio;
154 var splitR = 100 - splitL;
155
156 var diffLinesArr = [];
157 if (attr.diffLines && attr.diffLines.trim()) {
158 attr.diffLines.split(',').forEach(function (s) {
159 var n = parseInt(s.trim(), 10);
160 if (!isNaN(n)) diffLinesArr.push(n - 1);
161 });
162 }
163
164 var paneStyle = function (bg) { return {
165 flex: 1, backgroundColor: bg, overflow: 'hidden', fontFamily: '"Fira Code", "Cascadia Code", "JetBrains Mono", monospace'
166 }; };
167
168 var headerStyle = function (hbg) { return {
169 display: 'flex', alignItems: 'center', justifyContent: 'space-between',
170 padding: '8px 14px', backgroundColor: hbg, color: attr.headerColor, fontSize: '12px', fontWeight: 700, letterSpacing: '0.06em'
171 }; };
172
173 var typoVars = _tv(attr.typoCode, '--bkbg-cc-cd');
174 return el('div', {
175 style: Object.assign({
176 display: 'flex', borderRadius: attr.borderRadius + 'px', overflow: 'hidden',
177 position: 'relative'
178 }, typoVars)
179 },
180 el('div', { style: { ...paneStyle(attr.bgLeft), width: splitL + '%', flexShrink: 0, flexGrow: 0 } },
181 el('div', { style: headerStyle(attr.headerBgLeft) },
182 el('span', {}, attr.leftLabel),
183 attr.showLanguageBadge && el('span', { style: { opacity: 0.8, fontWeight: 400 } }, attr.leftLang),
184 attr.showCopyButtons && el('span', {
185 style: { cursor: 'pointer', background: 'rgba(255,255,255,0.2)', borderRadius: '4px', padding: '2px 8px', fontSize: '10px' }
186 }, 'Copy')
187 ),
188 renderCode(attr.leftCode, attr.leftLang, attr.showLineNumbers, attr.wrapLines, attr.codeColor, attr.lineNumColor, attr.maxHeight, attr.showDiffMarkers ? diffLinesArr : [], 'left')
189 ),
190 el('div', { style: { width: '3px', backgroundColor: 'rgba(128,128,128,0.3)', flexShrink: 0, cursor: 'col-resize' } }),
191 el('div', { style: { ...paneStyle(attr.bgRight), width: splitR + '%', flexShrink: 0, flexGrow: 0 } },
192 el('div', { style: headerStyle(attr.headerBgRight) },
193 el('span', {}, attr.rightLabel),
194 attr.showLanguageBadge && el('span', { style: { opacity: 0.8, fontWeight: 400 } }, attr.rightLang),
195 attr.showCopyButtons && el('span', {
196 style: { cursor: 'pointer', background: 'rgba(255,255,255,0.2)', borderRadius: '4px', padding: '2px 8px', fontSize: '10px' }
197 }, 'Copy')
198 ),
199 renderCode(attr.rightCode, attr.rightLang, attr.showLineNumbers, attr.wrapLines, attr.codeColor, attr.lineNumColor, attr.maxHeight, attr.showDiffMarkers ? diffLinesArr : [], 'right')
200 )
201 );
202 }
203
204 // ── Block registration ─────────────────────────────────────────────────────
205 wp.blocks.registerBlockType('blockenberg/code-comparison', {
206 title: 'Code Comparison',
207 icon: 'editor-code',
208 category: 'bkbg-dev',
209 edit: function (props) {
210 var attr = props.attributes;
211 var setAttr = props.setAttributes;
212
213 function applyTheme(themeKey) {
214 if (themeKey === 'custom') return;
215 var p = THEME_PRESETS[themeKey];
216 if (!p) return;
217 setAttr({ bgLeft: p.bgL, bgRight: p.bgR, headerBgLeft: p.hdL, headerBgRight: p.hdR, headerColor: p.hdColor, codeColor: p.code, lineNumColor: p.lineNum, diffAddBg: p.addBg, diffRemBg: p.remBg });
218 }
219
220 var blockProps = useBlockProps({ style: { fontFamily: 'inherit' } });
221
222 return el('div', blockProps,
223 el(InspectorControls, {},
224 // Code panels
225 el(PanelBody, { title: __('Left Pane (Before)', 'blockenberg'), initialOpen: true },
226 el(TextControl, {
227 __nextHasNoMarginBottom: true,
228 label: __('Label', 'blockenberg'),
229 value: attr.leftLabel,
230 onChange: function (v) { setAttr({ leftLabel: v }); }
231 }),
232 el(SelectControl, {
233 __nextHasNoMarginBottom: true,
234 label: __('Language', 'blockenberg'),
235 value: attr.leftLang,
236 options: LANGS,
237 onChange: function (v) { setAttr({ leftLang: v }); }
238 }),
239 el(TextareaControl, {
240 __nextHasNoMarginBottom: true,
241 label: __('Code', 'blockenberg'),
242 value: attr.leftCode,
243 rows: 10,
244 onChange: function (v) { setAttr({ leftCode: v }); }
245 })
246 ),
247 el(PanelBody, { title: __('Right Pane (After)', 'blockenberg'), initialOpen: false },
248 el(TextControl, {
249 __nextHasNoMarginBottom: true,
250 label: __('Label', 'blockenberg'),
251 value: attr.rightLabel,
252 onChange: function (v) { setAttr({ rightLabel: v }); }
253 }),
254 el(SelectControl, {
255 __nextHasNoMarginBottom: true,
256 label: __('Language', 'blockenberg'),
257 value: attr.rightLang,
258 options: LANGS,
259 onChange: function (v) { setAttr({ rightLang: v }); }
260 }),
261 el(TextareaControl, {
262 __nextHasNoMarginBottom: true,
263 label: __('Code', 'blockenberg'),
264 value: attr.rightCode,
265 rows: 10,
266 onChange: function (v) { setAttr({ rightCode: v }); }
267 })
268 ),
269 el(PanelBody, { title: __('Diff & Display', 'blockenberg'), initialOpen: false },
270 el(TextControl, {
271 __nextHasNoMarginBottom: true,
272 label: __('Highlighted Line Numbers (comma-separated)', 'blockenberg'),
273 help: __('e.g. 2,5,7 — marks those lines as changed in both panes', 'blockenberg'),
274 value: attr.diffLines,
275 onChange: function (v) { setAttr({ diffLines: v }); }
276 }),
277 el(ToggleControl, {
278 __nextHasNoMarginBottom: true,
279 label: __('Show Diff Markers (+/−)', 'blockenberg'),
280 checked: attr.showDiffMarkers,
281 onChange: function (v) { setAttr({ showDiffMarkers: v }); }
282 }),
283 el(ToggleControl, {
284 __nextHasNoMarginBottom: true,
285 label: __('Line Numbers', 'blockenberg'),
286 checked: attr.showLineNumbers,
287 onChange: function (v) { setAttr({ showLineNumbers: v }); }
288 }),
289 el(ToggleControl, {
290 __nextHasNoMarginBottom: true,
291 label: __('Wrap Long Lines', 'blockenberg'),
292 checked: attr.wrapLines,
293 onChange: function (v) { setAttr({ wrapLines: v }); }
294 }),
295 el(ToggleControl, {
296 __nextHasNoMarginBottom: true,
297 label: __('Copy Buttons', 'blockenberg'),
298 checked: attr.showCopyButtons,
299 onChange: function (v) { setAttr({ showCopyButtons: v }); }
300 }),
301 el(ToggleControl, {
302 __nextHasNoMarginBottom: true,
303 label: __('Language Badge', 'blockenberg'),
304 checked: attr.showLanguageBadge,
305 onChange: function (v) { setAttr({ showLanguageBadge: v }); }
306 }),
307 el(ToggleControl, {
308 __nextHasNoMarginBottom: true,
309 label: __('Draggable Divider', 'blockenberg'),
310 checked: attr.draggableSplit,
311 onChange: function (v) { setAttr({ draggableSplit: v }); }
312 })
313 ),
314 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: false },
315 el(RangeControl, {
316 __nextHasNoMarginBottom: true,
317 label: __('Left Pane Width %', 'blockenberg'),
318 value: attr.splitRatio,
319 min: 20, max: 80,
320 onChange: function (v) { setAttr({ splitRatio: v }); }
321 }),
322 el(RangeControl, {
323 __nextHasNoMarginBottom: true,
324 label: __('Max Height (0 = unlimited)', 'blockenberg'),
325 value: attr.maxHeight,
326 min: 0, max: 1000, step: 20,
327 onChange: function (v) { setAttr({ maxHeight: v }); }
328 }),
329 el(RangeControl, {
330 __nextHasNoMarginBottom: true,
331 label: __('Border Radius (px)', 'blockenberg'),
332 value: attr.borderRadius,
333 min: 0, max: 32,
334 onChange: function (v) { setAttr({ borderRadius: v }); }
335 })
336 ),
337 el(PanelBody, { title: __('Theme', 'blockenberg'), initialOpen: false },
338 el(SelectControl, {
339 __nextHasNoMarginBottom: true,
340 label: __('Preset Theme', 'blockenberg'),
341 value: attr.theme,
342 options: THEMES,
343 onChange: function (v) { setAttr({ theme: v }); applyTheme(v); }
344 })
345 ),
346
347 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
348 el(getTypographyControl(), { label: __('Code Text', 'blockenberg'), value: attr.typoCode, onChange: function (v) { setAttr({ typoCode: v }); } }),
349 el(RangeControl, {
350 __nextHasNoMarginBottom: true,
351 label: __('Font Size (px)', 'blockenberg'),
352 value: attr.fontSize,
353 min: 10, max: 22,
354 onChange: function (v) { setAttr({ fontSize: v }); }
355 })
356 ),
357 el(PanelColorSettings, {
358 title: __('Colors', 'blockenberg'),
359 initialOpen: false,
360 colorSettings: [
361 { label: __('Left Pane Background', 'blockenberg'), value: attr.bgLeft, onChange: function (v) { setAttr({ bgLeft: v || '' }); } },
362 { label: __('Right Pane Background', 'blockenberg'), value: attr.bgRight, onChange: function (v) { setAttr({ bgRight: v || '' }); } },
363 { label: __('Left Header', 'blockenberg'), value: attr.headerBgLeft, onChange: function (v) { setAttr({ headerBgLeft: v || '' }); } },
364 { label: __('Right Header', 'blockenberg'), value: attr.headerBgRight, onChange: function (v) { setAttr({ headerBgRight: v || '' }); } },
365 { label: __('Header Text', 'blockenberg'), value: attr.headerColor, onChange: function (v) { setAttr({ headerColor: v || '' }); } },
366 { label: __('Code Text', 'blockenberg'), value: attr.codeColor, onChange: function (v) { setAttr({ codeColor: v || '' }); } },
367 { label: __('Line Number Color', 'blockenberg'), value: attr.lineNumColor, onChange: function (v) { setAttr({ lineNumColor: v || '' }); } }
368 ]
369 })
370 ),
371 el(ComparisonPreview, { attr: attr })
372 );
373 },
374 save: function (props) {
375 var attr = props.attributes;
376 return el('div', useBlockProps.save(),
377 el('div', {
378 className: 'bkbg-cc-app',
379 'data-opts': JSON.stringify(attr)
380 })
381 );
382 }
383 });
384 }() );
385