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

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

379 lines 18.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 useState = wp.element.useState;
5 var registerBlockType = wp.blocks.registerBlockType;
6 var __ = wp.i18n.__;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var PanelBody = wp.components.PanelBody;
11 var RangeControl = wp.components.RangeControl;
12 var SelectControl = wp.components.SelectControl;
13 var TextareaControl = wp.components.TextareaControl;
14 var TextControl = wp.components.TextControl;
15 var ToggleControl = wp.components.ToggleControl;
16
17 var _tc; function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
18 var _tv; function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
19 var _tvf = function (typo, prefix) { var fn = getTypoCssVars(); return fn ? fn(typo, prefix) : {}; };
20
21 // Build spans from text, wrapping matching phrases with highlight spans
22 function buildHighlightedText(text, highlights, style, hlColor, opacity, radius, padX, padY, lineThick, linePos, animate) {
23 if (!text) return [];
24
25 var phrases = (highlights || '').split(',').map(function (s) { return s.trim(); }).filter(Boolean);
26
27 if (!phrases.length) return [text];
28
29 // Sort by length desc so longer phrases matched first
30 phrases.sort(function (a, b) { return b.length - a.length; });
31
32 // Build regex from phrases (case insensitive)
33 var pattern = phrases.map(function (p) {
34 return p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
35 }).join('|');
36 var regex = new RegExp('(' + pattern + ')', 'gi');
37
38 var parts = text.split(regex);
39 var opacityFrac = (opacity || 100) / 100;
40
41 return parts.map(function (part, i) {
42 if (!part) return null;
43 var isMatch = phrases.some(function (p) { return p.toLowerCase() === part.toLowerCase(); });
44
45 if (!isMatch) return el('span', { key: i }, part);
46
47 var hlStyles = {
48 display: 'inline',
49 position: 'relative',
50 zIndex: 0
51 };
52
53 var markStyles = {};
54
55 if (style === 'marker') {
56 markStyles = {
57 background: hlColor + Math.round(opacityFrac * 255).toString(16).padStart(2, '0'),
58 borderRadius: radius + 'px',
59 padding: padY + 'px ' + padX + 'px',
60 display: 'inline',
61 position: 'relative',
62 zIndex: -1
63 };
64 } else if (style === 'underline') {
65 markStyles = {
66 borderBottom: lineThick + 'px solid ' + hlColor,
67 display: 'inline',
68 paddingBottom: '2px'
69 };
70 } else if (style === 'strikethrough') {
71 markStyles = {
72 textDecoration: 'line-through',
73 textDecorationColor: hlColor,
74 textDecorationThickness: lineThick + 'px'
75 };
76 } else if (style === 'box') {
77 markStyles = {
78 outline: lineThick + 'px solid ' + hlColor,
79 borderRadius: radius + 'px',
80 padding: padY + 'px ' + padX + 'px',
81 display: 'inline'
82 };
83 } else if (style === 'glow') {
84 markStyles = {
85 textShadow: '0 0 ' + (lineThick * 3) + 'px ' + hlColor,
86 display: 'inline'
87 };
88 }
89
90 return el('mark', {
91 key: i,
92 className: 'bkth-hl bkth-hl--' + style + (animate ? ' bkth-animate' : ' bkth-revealed'),
93 style: markStyles
94 }, part);
95 }).filter(Boolean);
96 }
97
98 registerBlockType('blockenberg/text-highlight', {
99
100 edit: function (props) {
101 var attrs = props.attributes;
102 var setAttr = props.setAttributes;
103
104 var Tag = attrs.tag || 'h2';
105
106 var textStyle = {
107 textAlign: attrs.textAlign,
108 color: attrs.textColor,
109 maxWidth: attrs.maxWidth + 'px',
110 margin: '0 auto'
111 };
112
113 var rendered = buildHighlightedText(
114 attrs.text, attrs.highlights,
115 attrs.highlightStyle, attrs.highlightColor,
116 attrs.highlightOpacity, attrs.highlightRadius,
117 attrs.highlightPadX, attrs.highlightPadY,
118 attrs.lineThickness, attrs.linePosition,
119 false // editor: always show revealed
120 );
121
122 var blockProps = useBlockProps((function () {
123 var s = {
124 paddingTop: attrs.paddingTop + 'px',
125 paddingBottom: attrs.paddingBottom + 'px'
126 };
127 if (attrs.bgColor) s.background = attrs.bgColor;
128 Object.assign(s, _tvf(attrs.titleTypo, '--bkth-tt-'));
129 return { className: 'bkth-wrap', style: s };
130 })());
131
132 return el(Fragment, null,
133 el(InspectorControls, null,
134
135 el(PanelBody, { title: __('Text', 'blockenberg'), initialOpen: true },
136 el(TextareaControl, {
137 label: __('Text', 'blockenberg'),
138 rows: 4,
139 value: attrs.text,
140 onChange: function (v) { setAttr({ text: v }); }
141 }),
142 el(TextControl, {
143 label: __('Highlight phrases (comma-separated)', 'blockenberg'),
144 help: __('e.g. "grow faster, smarter marketing"', 'blockenberg'),
145 value: attrs.highlights,
146 onChange: function (v) { setAttr({ highlights: v }); }
147 }),
148 el(SelectControl, {
149 label: __('HTML Tag', 'blockenberg'),
150 value: attrs.tag,
151 options: [
152 { label: 'H1', value: 'h1' },
153 { label: 'H2', value: 'h2' },
154 { label: 'H3', value: 'h3' },
155 { label: 'H4', value: 'h4' },
156 { label: 'Paragraph', value: 'p' },
157 { label: 'Div', value: 'div' }
158 ],
159 onChange: function (v) { setAttr({ tag: v }); }
160 })
161 ),
162
163 el(PanelBody, { title: __('Highlight Style', 'blockenberg'), initialOpen: true },
164 el(SelectControl, {
165 label: __('Style', 'blockenberg'),
166 value: attrs.highlightStyle,
167 options: [
168 { label: 'Marker (background fill)', value: 'marker' },
169 { label: 'Underline', value: 'underline' },
170 { label: 'Box outline', value: 'box' },
171 { label: 'Strikethrough', value: 'strikethrough' },
172 { label: 'Glow', value: 'glow' }
173 ],
174 onChange: function (v) { setAttr({ highlightStyle: v }); }
175 }),
176 (attrs.highlightStyle === 'marker' || attrs.highlightStyle === 'box') && el(RangeControl, {
177 label: __('Corner Radius', 'blockenberg'),
178 value: attrs.highlightRadius, min: 0, max: 24,
179 onChange: function (v) { setAttr({ highlightRadius: v }); }
180 }),
181 (attrs.highlightStyle === 'marker') && el(RangeControl, {
182 label: __('Horizontal Padding', 'blockenberg'),
183 value: attrs.highlightPadX, min: 0, max: 24,
184 onChange: function (v) { setAttr({ highlightPadX: v }); }
185 }),
186 (attrs.highlightStyle === 'marker') && el(RangeControl, {
187 label: __('Vertical Padding', 'blockenberg'),
188 value: attrs.highlightPadY, min: 0, max: 24,
189 onChange: function (v) { setAttr({ highlightPadY: v }); }
190 }),
191 (attrs.highlightStyle !== 'marker' && attrs.highlightStyle !== 'glow') && el(RangeControl, {
192 label: __('Line / Border Thickness', 'blockenberg'),
193 value: attrs.lineThickness, min: 1, max: 20,
194 onChange: function (v) { setAttr({ lineThickness: v }); }
195 }),
196 el(RangeControl, {
197 label: __('Opacity (%)', 'blockenberg'),
198 value: attrs.highlightOpacity, min: 10, max: 100,
199 onChange: function (v) { setAttr({ highlightOpacity: v }); }
200 })
201 ),
202
203 el(PanelBody, { title: __('Animation', 'blockenberg'), initialOpen: false },
204 el(ToggleControl, {
205 label: __('Animate on scroll', 'blockenberg'),
206 checked: attrs.animateOnScroll,
207 onChange: function (v) { setAttr({ animateOnScroll: v }); },
208 __nextHasNoMarginBottom: true
209 }),
210 attrs.animateOnScroll && el(SelectControl, {
211 label: __('Animation style', 'blockenberg'),
212 value: attrs.animationStyle,
213 options: [
214 { label: 'Sweep (left to right)', value: 'sweep' },
215 { label: 'Fade in', value: 'fade' },
216 { label: 'Scale up', value: 'scale' },
217 { label: 'Pop', value: 'pop' }
218 ],
219 onChange: function (v) { setAttr({ animationStyle: v }); }
220 }),
221 attrs.animateOnScroll && el(RangeControl, {
222 label: __('Duration (ms)', 'blockenberg'),
223 value: attrs.animationDuration, min: 100, max: 2000,
224 onChange: function (v) { setAttr({ animationDuration: v }); }
225 }),
226 attrs.animateOnScroll && el(RangeControl, {
227 label: __('Stagger delay (ms)', 'blockenberg'),
228 value: attrs.animationDelay, min: 0, max: 400,
229 onChange: function (v) { setAttr({ animationDelay: v }); }
230 }),
231 attrs.animateOnScroll && el(RangeControl, {
232 label: __('Scroll threshold (0.0–1.0)', 'blockenberg'),
233 value: attrs.threshold, min: 0, max: 1, step: 0.05,
234 onChange: function (v) { setAttr({ threshold: v }); }
235 }),
236 attrs.animateOnScroll && el(ToggleControl, {
237 label: __('Repeat on re-enter', 'blockenberg'),
238 checked: attrs.repeat,
239 onChange: function (v) { setAttr({ repeat: v }); },
240 __nextHasNoMarginBottom: true
241 })
242 ),
243
244 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
245 getTypoControl() && getTypoControl()({
246 label: __('Title', 'blockenberg'),
247 value: attrs.titleTypo || {},
248 onChange: function (v) { setAttr({ titleTypo: v }); }
249 }),
250 el(SelectControl, {
251 label: __('Text Align', 'blockenberg'),
252 value: attrs.textAlign,
253 options: [
254 { label: 'Left', value: 'left' },
255 { label: 'Center', value: 'center' },
256 { label: 'Right', value: 'right' }
257 ],
258 onChange: function (v) { setAttr({ textAlign: v }); }
259 })
260 ),
261
262 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: false },
263 el(RangeControl, {
264 label: __('Max Width (px)', 'blockenberg'),
265 value: attrs.maxWidth, min: 300, max: 1600,
266 onChange: function (v) { setAttr({ maxWidth: v }); }
267 }),
268 el(RangeControl, {
269 label: __('Padding Top (px)', 'blockenberg'),
270 value: attrs.paddingTop, min: 0, max: 200,
271 onChange: function (v) { setAttr({ paddingTop: v }); }
272 }),
273 el(RangeControl, {
274 label: __('Padding Bottom (px)', 'blockenberg'),
275 value: attrs.paddingBottom, min: 0, max: 200,
276 onChange: function (v) { setAttr({ paddingBottom: v }); }
277 })
278 ),
279
280 el(PanelColorSettings, {
281 title: __('Colors', 'blockenberg'),
282 initialOpen: false,
283 colorSettings: [
284 { label: __('Highlight Color', 'blockenberg'), value: attrs.highlightColor, onChange: function (v) { setAttr({ highlightColor: v || '#fde68a' }); } },
285 { label: __('Text Color', 'blockenberg'), value: attrs.textColor, onChange: function (v) { setAttr({ textColor: v || '#111827' }); } },
286 { label: __('Background', 'blockenberg'), value: attrs.bgColor, onChange: function (v) { setAttr({ bgColor: v || '' }); } }
287 ]
288 })
289 ),
290
291 el('div', blockProps,
292 el(Tag, { className: 'bkth-title', style: textStyle }, rendered)
293 )
294 );
295 },
296
297 save: function (props) {
298 var a = props.attributes;
299 var Tag = a.tag || 'h2';
300 var phrases = (a.highlights || '').split(',').map(function (s) { return s.trim(); }).filter(Boolean);
301
302 var wrapStyle = {
303 paddingTop: a.paddingTop + 'px',
304 paddingBottom: a.paddingBottom + 'px',
305 '--bkth-hl-color': a.highlightColor,
306 '--bkth-hl-dur': a.animationDuration + 'ms',
307 '--bkth-hl-delay': a.animationDelay + 'ms'
308 };
309 if (a.bgColor) wrapStyle.background = a.bgColor;
310
311 var textStyle = {
312 textAlign: a.textAlign,
313 color: a.textColor,
314 maxWidth: a.maxWidth + 'px',
315 margin: '0 auto'
316 };
317
318 var blockProps = wp.blockEditor.useBlockProps.save((function () {
319 var s = Object.assign({}, wrapStyle);
320 Object.assign(s, _tvf(a.titleTypo, '--bkth-tt-'));
321 return {
322 className: 'bkth-wrap',
323 style: s,
324 'data-animate': a.animateOnScroll ? '1' : '0',
325 'data-anim-style': a.animationStyle,
326 'data-threshold': a.threshold,
327 'data-repeat': a.repeat ? '1' : '0'
328 };
329 })());
330
331 if (!phrases.length) {
332 return el('div', blockProps, el(Tag, { className: 'bkth-title', style: textStyle }, a.text));
333 }
334
335 phrases.sort(function (a2, b) { return b.length - a2.length; });
336 var pattern = phrases.map(function (p) { return p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }).join('|');
337 var regex = new RegExp('(' + pattern + ')', 'gi');
338 var parts = a.text.split(regex);
339
340 var opacityHex = Math.round((a.highlightOpacity / 100) * 255).toString(16).padStart(2, '0');
341 var hlIdx = 0;
342
343 var spans = parts.map(function (part, i) {
344 if (!part) return null;
345 var isMatch = phrases.some(function (p) { return p.toLowerCase() === part.toLowerCase(); });
346 if (!isMatch) return el('span', { key: i }, part);
347
348 var hlStyle = {};
349 var hs = a.highlightStyle;
350 if (hs === 'marker') {
351 hlStyle = {
352 background: a.highlightColor + opacityHex,
353 borderRadius: a.highlightRadius + 'px',
354 padding: a.highlightPadY + 'px ' + a.highlightPadX + 'px'
355 };
356 } else if (hs === 'underline') {
357 hlStyle = { borderBottom: a.lineThickness + 'px solid ' + a.highlightColor, paddingBottom: '2px' };
358 } else if (hs === 'strikethrough') {
359 hlStyle = { textDecoration: 'line-through', textDecorationColor: a.highlightColor, textDecorationThickness: a.lineThickness + 'px' };
360 } else if (hs === 'box') {
361 hlStyle = { outline: a.lineThickness + 'px solid ' + a.highlightColor, borderRadius: a.highlightRadius + 'px', padding: a.highlightPadY + 'px ' + a.highlightPadX + 'px' };
362 } else if (hs === 'glow') {
363 hlStyle = { textShadow: '0 0 ' + (a.lineThickness * 3) + 'px ' + a.highlightColor };
364 }
365
366 var idx = hlIdx++;
367 return el('mark', {
368 key: i,
369 className: 'bkth-hl bkth-hl--' + hs + (a.animateOnScroll ? ' bkth-animate' : ' bkth-revealed'),
370 style: hlStyle,
371 'data-bkth-i': idx
372 }, part);
373 }).filter(Boolean);
374
375 return el('div', blockProps, el(Tag, { className: 'bkth-title', style: textStyle }, spans));
376 }
377 });
378 }() );
379