PluginProbe ʕ •ᴥ•ʔ
FrontBlocks for Gutenberg/GeneratePress / 1.5.0
FrontBlocks for Gutenberg/GeneratePress v1.5.0
1.5.2 1.5.1 1.4.0 1.5.0 trunk 0.2.0 0.2.1 0.2.2 0.2.3 0.2.4 0.2.5 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 ci-artifacts
frontblocks / assets / user-text / frontblocks-user-text-option.jsx
frontblocks / assets / user-text Last commit date
frontblocks-user-text-option.jsx 2 months ago frontblocks-user-text.css 2 months ago frontblocks-user-text.js 2 months ago
frontblocks-user-text-option.jsx
262 lines
1 const { registerBlockType } = wp.blocks;
2 const { Fragment, useState, useEffect } = wp.element;
3 const { useSelect } = wp.data;
4 const {
5 InspectorControls,
6 useBlockProps,
7 PanelColorSettings,
8 } = wp.blockEditor;
9 const {
10 PanelBody,
11 SelectControl,
12 TextControl,
13 TextareaControl,
14 } = wp.components;
15 const { __ } = wp.i18n;
16
17 const HEADING_TAGS = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ];
18
19 const TAG_OPTIONS = [
20 { label: 'p', value: 'p' },
21 { label: 'h1', value: 'h1' },
22 { label: 'h2', value: 'h2' },
23 { label: 'h3', value: 'h3' },
24 { label: 'h4', value: 'h4' },
25 { label: 'h5', value: 'h5' },
26 { label: 'h6', value: 'h6' },
27 { label: 'span', value: 'span' },
28 { label: 'div', value: 'div' },
29 ];
30
31 const FONT_WEIGHT_OPTIONS = [
32 { label: __( 'Default', 'frontblocks' ), value: '' },
33 { label: __( 'Thin (100)', 'frontblocks' ), value: '100' },
34 { label: __( 'Extra Light (200)', 'frontblocks' ), value: '200' },
35 { label: __( 'Light (300)', 'frontblocks' ), value: '300' },
36 { label: __( 'Normal (400)', 'frontblocks' ), value: '400' },
37 { label: __( 'Medium (500)', 'frontblocks' ), value: '500' },
38 { label: __( 'Semi Bold (600)', 'frontblocks' ), value: '600' },
39 { label: __( 'Bold (700)', 'frontblocks' ), value: '700' },
40 { label: __( 'Extra Bold (800)', 'frontblocks' ), value: '800' },
41 { label: __( 'Black (900)', 'frontblocks' ), value: '900' },
42 ];
43
44 const TEXT_ALIGN_OPTIONS = [
45 { label: __( 'Default', 'frontblocks' ), value: '' },
46 { label: __( 'Left', 'frontblocks' ), value: 'left' },
47 { label: __( 'Center', 'frontblocks' ), value: 'center' },
48 { label: __( 'Right', 'frontblocks' ), value: 'right' },
49 { label: __( 'Justify', 'frontblocks' ), value: 'justify' },
50 ];
51
52 /**
53 * Resolve a CSS var like "var(--wp--preset--font-size--large)" to a real value
54 * using computed styles on the document root.
55 */
56 function resolveCssVar( value ) {
57 if ( ! value || ! value.startsWith( 'var(' ) ) return value;
58 const match = value.match( /var\(\s*(--[^,)]+)/ );
59 if ( ! match ) return value;
60 const resolved = getComputedStyle( document.documentElement )
61 .getPropertyValue( match[1] )
62 .trim();
63 return resolved || value;
64 }
65
66 registerBlockType( 'frontblocks/user-text', {
67 title: __( 'User Text', 'frontblocks' ),
68 description: __( 'Text pattern with logged-in user data placeholders.', 'frontblocks' ),
69 category: 'text',
70 icon: 'admin-users',
71 supports: { html: false },
72
73 attributes: {
74 textPattern: { type: 'string', default: __( 'Hello, {nombre}!', 'frontblocks' ) },
75 htmlTag: { type: 'string', default: 'p' },
76 textColor: { type: 'string', default: '' },
77 hoverTextColor: { type: 'string', default: '' },
78 fontSize: { type: 'string', default: '' },
79 fontFamily: { type: 'string', default: '' },
80 fontWeight: { type: 'string', default: '' },
81 textAlign: { type: 'string', default: '' },
82 loggedOutText: { type: 'string', default: '' },
83 },
84
85 edit: ( { attributes, setAttributes } ) => {
86 const {
87 textPattern, htmlTag, textColor, hoverTextColor, fontSize,
88 fontFamily, fontWeight, textAlign, loggedOutText,
89 } = attributes;
90
91 // ── Current user (with email, needs context=edit) ──────────────────
92 const [ currentUser, setCurrentUser ] = useState( null );
93 useEffect( () => {
94 wp.apiFetch( { path: '/wp/v2/users/me?context=edit' } )
95 .then( ( u ) => setCurrentUser( u ) )
96 .catch( () => {} );
97 }, [] );
98
99 // ── Theme global styles ─────────────────────────────────────────────
100 const [ themeTypo, setThemeTypo ] = useState( {} );
101 const themeSlug = useSelect( ( select ) => select( 'core' ).getCurrentTheme()?.stylesheet );
102
103 useEffect( () => {
104 if ( ! themeSlug ) return;
105 wp.apiFetch( { path: '/wp/v2/global-styles/themes/' + themeSlug } )
106 .then( ( data ) => {
107 const elements = ( data && data.styles && data.styles.elements ) ? data.styles.elements : {};
108 // Merge heading base + specific tag (specific wins)
109 const base = HEADING_TAGS.includes( htmlTag )
110 ? ( elements.heading && elements.heading.typography ? elements.heading.typography : {} )
111 : {};
112 const tagTypo = ( elements[ htmlTag ] && elements[ htmlTag ].typography )
113 ? elements[ htmlTag ].typography
114 : {};
115 const merged = Object.assign( {}, base, tagTypo );
116 // Resolve CSS vars to real values for display
117 const resolved = {};
118 Object.keys( merged ).forEach( ( k ) => {
119 resolved[ k ] = resolveCssVar( merged[ k ] );
120 } );
121 setThemeTypo( resolved );
122 } )
123 .catch( () => {} );
124 }, [ themeSlug, htmlTag ] );
125
126 // ── Preview: replace placeholders with real user data ───────────────
127 const previewText = ( pattern ) => {
128 if ( ! currentUser ) return pattern;
129 const nombre = currentUser.first_name || currentUser.name || '';
130 const map = {
131 '{nombre}': nombre,
132 '{apellido}': currentUser.last_name || '',
133 '{display_name}': currentUser.name || '',
134 '{email}': currentUser.email || '',
135 '{username}': currentUser.slug || '',
136 '{bio}': currentUser.description || '',
137 '{web}': currentUser.url || '',
138 };
139 return Object.entries( map ).reduce(
140 ( str, [ key, val ] ) => str.split( key ).join( val ),
141 pattern
142 );
143 };
144
145 // ── Inline styles: only explicit overrides ──────────────────────────
146 const inlineStyle = {
147 color: textColor || undefined,
148 fontSize: fontSize || undefined,
149 fontFamily: fontFamily || undefined,
150 fontWeight: fontWeight || undefined,
151 textAlign: textAlign || undefined,
152 };
153
154 const blockProps = useBlockProps();
155 const Tag = htmlTag || 'p';
156
157 // Helper: placeholder showing theme default when field is empty
158 const themePlaceholder = ( key, fallback ) => {
159 if ( ! themeTypo[ key ] ) return fallback;
160 return themeTypo[ key ] + ' (' + __( 'tema', 'frontblocks' ) + ')';
161 };
162
163 // For SelectControl (fontWeight): inject theme default into label
164 const weightOptions = FONT_WEIGHT_OPTIONS.map( ( opt ) => {
165 if ( opt.value !== '' ) return opt;
166 const tw = themeTypo.fontWeight;
167 return {
168 ...opt,
169 label: tw
170 ? __( 'Default', 'frontblocks' ) + '' + tw
171 : __( 'Default', 'frontblocks' ),
172 };
173 } );
174
175 return (
176 <Fragment>
177 <InspectorControls>
178 <PanelBody title={ __( 'Pattern & Data', 'frontblocks' ) } initialOpen={ true }>
179 <TextareaControl
180 label={ __( 'Text Pattern', 'frontblocks' ) }
181 value={ textPattern }
182 onChange={ ( val ) => setAttributes( { textPattern: val } ) }
183 rows={ 4 }
184 help={ __( 'Placeholders: {nombre}, {apellido}, {display_name}, {email}, {username}, {bio}, {web}', 'frontblocks' ) }
185 />
186 <TextControl
187 label={ __( 'Logged-out Fallback', 'frontblocks' ) }
188 value={ loggedOutText }
189 onChange={ ( val ) => setAttributes( { loggedOutText: val } ) }
190 help={ __( 'Shown when no user is logged in. Leave empty to hide.', 'frontblocks' ) }
191 />
192 </PanelBody>
193
194 <PanelBody title={ __( 'Typography', 'frontblocks' ) } initialOpen={ false }>
195 <SelectControl
196 label={ __( 'HTML Tag', 'frontblocks' ) }
197 value={ htmlTag }
198 options={ TAG_OPTIONS }
199 onChange={ ( val ) => setAttributes( { htmlTag: val } ) }
200 />
201 <TextControl
202 label={ __( 'Font Size', 'frontblocks' ) }
203 value={ fontSize }
204 onChange={ ( val ) => setAttributes( { fontSize: val } ) }
205 placeholder={ themePlaceholder( 'fontSize', '16px, 1.5rem…' ) }
206 help={ fontSize
207 ? __( 'Custom override active. Clear to use theme default.', 'frontblocks' )
208 : __( 'Empty = theme default.', 'frontblocks' )
209 }
210 />
211 <SelectControl
212 label={ __( 'Font Weight', 'frontblocks' ) }
213 value={ fontWeight }
214 options={ weightOptions }
215 onChange={ ( val ) => setAttributes( { fontWeight: val } ) }
216 />
217 <TextControl
218 label={ __( 'Font Family', 'frontblocks' ) }
219 value={ fontFamily }
220 onChange={ ( val ) => setAttributes( { fontFamily: val } ) }
221 placeholder={ themePlaceholder( 'fontFamily', 'Inter, sans-serif…' ) }
222 help={ fontFamily
223 ? __( 'Custom override active. Clear to use theme default.', 'frontblocks' )
224 : __( 'Empty = theme default.', 'frontblocks' )
225 }
226 />
227 <SelectControl
228 label={ __( 'Text Align', 'frontblocks' ) }
229 value={ textAlign }
230 options={ TEXT_ALIGN_OPTIONS }
231 onChange={ ( val ) => setAttributes( { textAlign: val } ) }
232 />
233 </PanelBody>
234
235 <PanelColorSettings
236 title={ __( 'Color', 'frontblocks' ) }
237 initialOpen={ false }
238 colorSettings={ [
239 {
240 value: textColor,
241 onChange: ( val ) => setAttributes( { textColor: val || '' } ),
242 label: __( 'Text Color', 'frontblocks' ),
243 },
244 {
245 value: hoverTextColor,
246 onChange: ( val ) => setAttributes( { hoverTextColor: val || '' } ),
247 label: __( 'Text Color on Hover', 'frontblocks' ),
248 },
249 ] }
250 />
251 </InspectorControls>
252
253 <Tag { ...blockProps } style={ { ...inlineStyle, margin: 0 } }>
254 { previewText( textPattern ) || __( 'Enter a text pattern in the sidebar…', 'frontblocks' ) }
255 </Tag>
256 </Fragment>
257 );
258 },
259
260 save: () => null,
261 } );
262