PluginProbe ʕ •ᴥ•ʔ
GenerateBlocks / 2.2.0
GenerateBlocks v2.2.0
trunk 1.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.3.0
generateblocks / src / blocks / text / edit.js
generateblocks / src / blocks / text Last commit date
components 6 months ago block.json 1 year ago edit.js 1 year ago editor.scss 1 year ago index.js 1 year ago save.js 1 year ago transforms.js 1 year ago
edit.js
281 lines
1 import { __ } from '@wordpress/i18n';
2 import { RichText, useBlockProps, InspectorControls } from '@wordpress/block-editor';
3 import { Platform, useEffect, useMemo } from '@wordpress/element';
4 import { compose } from '@wordpress/compose';
5
6 import { BlockStyles, withUniqueId } from '@edge22/block-styles';
7
8 import { withDynamicTag } from '../../hoc/withDynamicTag';
9 import { Icon } from './components/Icon.jsx';
10 import RootElement from '../../components/root-element/index.js';
11 import { BlockSettings } from './components/BlockSettings';
12 import { selectorShortcuts } from '@utils/selectorShortcuts';
13 import { withStyles } from '@hoc/withStyles';
14 import { BlockStylesBuilder } from '@components/block-styles-builder/BlockStylesBuilder';
15 import { AlignmentToolbar, LinkBlockToolbar, StylesOnboarder, TagNameToolbar } from '@components/index';
16 import { withHtmlAttributes } from '@hoc/withHtmlAttributes';
17 import { getBlockClasses } from '@utils/getBlockClasses';
18 import { DynamicTagBlockToolbar } from '../../dynamic-tags';
19
20 function EditBlock( props ) {
21 const {
22 attributes,
23 setAttributes,
24 mergeBlocks,
25 onReplace,
26 dynamicTagValue,
27 setContentMode,
28 contentMode,
29 name,
30 clientId,
31 onStyleChange,
32 getStyleValue,
33 editorHtmlAttributes,
34 isSelected,
35 styles,
36 context,
37 } = props;
38
39 const {
40 tagName,
41 content,
42 icon,
43 iconLocation,
44 iconOnly,
45 htmlAttributes,
46 } = attributes;
47
48 useEffect( () => {
49 if ( ! tagName ) {
50 setAttributes( { tagName: 'p' } );
51 }
52 }, [ tagName ] );
53
54 const contentValue = useMemo( () => {
55 if ( ! dynamicTagValue ) {
56 return content;
57 }
58
59 return dynamicTagValue.reduce( ( acc, { original, replacement, fallback } ) => {
60 if ( ! replacement ) {
61 return acc.replaceAll( original, fallback );
62 }
63
64 const replacementWithNoLinks = replacement.replace( /href="[^"]*"/g, 'href="#"' );
65
66 return acc.replaceAll( original, replacementWithNoLinks );
67 }, content );
68 }, [ dynamicTagValue, content ] );
69
70 const classNames = getBlockClasses(
71 'gb-text',
72 {
73 ...attributes,
74 styles,
75 },
76 ! icon
77 );
78
79 const blockProps = useBlockProps(
80 {
81 className: classNames.join( ' ' ).trim(),
82 ...editorHtmlAttributes,
83 }
84 );
85
86 const TagNameWithIcon = tagName || 'p';
87 const richTextProps = {
88 identifier: 'content',
89 value: contentValue,
90 onChange: ( value ) => setAttributes( { content: value } ),
91 onMerge: mergeBlocks,
92 onReplace,
93 onRemove: () => onReplace( [] ),
94 placeholder: __( 'Text', 'generateblocks' ),
95 withoutInteractiveFormatting: 'a' === tagName || 'button' === tagName,
96 ...( Platform.isNative && { deleteEnter: true } ), // setup RichText on native mobile to delete the "Enter" key as it's handled by the JS/RN side
97 };
98 const shortcuts = useMemo( () => {
99 const visibleSelectors = [];
100 const blockSelectors = { ...selectorShortcuts };
101
102 if ( 'a' !== tagName && 'button' !== tagName ) {
103 visibleSelectors.push(
104 {
105 label: __( 'Links', 'generateblocks' ),
106 value: 'a',
107 }
108 );
109 }
110
111 if ( icon ) {
112 visibleSelectors.push(
113 {
114 label: __( 'Icon', 'generateblocks' ),
115 value: '.gb-shape svg',
116 },
117 );
118
119 if ( blockSelectors?.default?.items ) {
120 blockSelectors.default.items.push(
121 { label: __( 'Icon', 'generateblocks' ), value: '.gb-shape svg' },
122 { label: __( 'Hovered icon', 'generateblocks' ), value: '&:is(:hover, :focus) .gb-shape svg' },
123 );
124 }
125
126 blockSelectors.icons = {
127 label: __( 'Icon', 'generateblocks' ),
128 items: [
129 { label: __( 'Icon', 'generateblocks' ), value: '.gb-shape svg' },
130 { label: __( 'Hovered icon', 'generateblocks' ), value: '&:is(:hover, :focus) .gb-shape svg' },
131 ],
132 };
133 }
134
135 if ( 'a' === tagName || 'button' === tagName ) {
136 if ( blockSelectors?.links ) {
137 delete blockSelectors.links;
138 }
139
140 const defaultItems = blockSelectors?.default?.items || [];
141
142 if ( defaultItems.length > 0 ) {
143 blockSelectors.default.items = defaultItems.filter( ( item ) => {
144 return 'a' !== item.value && ! item.value.startsWith( 'a:' );
145 } );
146 }
147 }
148
149 return {
150 selectorShortcuts: blockSelectors,
151 visibleShortcuts: visibleSelectors,
152 };
153 }, [ tagName, icon ] );
154
155 const renderContent = ( elementTagName, withBlockProps = false ) => {
156 if ( 'preview' === contentMode && dynamicTagValue ) {
157 const ElementTagName = elementTagName;
158
159 // Render a plain HTML tag in preview mode
160 return (
161 <ElementTagName
162 { ...( withBlockProps && blockProps ) }
163 dangerouslySetInnerHTML={ { __html: contentValue } }
164 />
165 );
166 }
167
168 // The RichText component can't handle the `<button>` tag for some reason.
169 // It doesn't allow spaces to be entered, and doesn't allow the user to type if there
170 // isn't already a value entered. To handle this, we'll render a `<button>` tag instead
171 // and use a `span` as the RichText component tag.
172 if ( 'button' === elementTagName ) {
173 return (
174 <button
175 { ...( withBlockProps && blockProps ) }
176 >
177 <RichText
178 { ...richTextProps }
179 tagName={ 'span' }
180 />
181 </button>
182 );
183 }
184
185 return (
186 <RichText
187 { ...richTextProps }
188 { ...( withBlockProps && blockProps ) }
189 tagName={ elementTagName }
190 />
191 );
192 };
193
194 return (
195 <>
196 <TagNameToolbar
197 label={ __( 'Choose tag name', 'generateblocks' ) }
198 tagName={ tagName }
199 onChange={ ( value ) => setAttributes( { tagName: value } ) }
200 />
201
202 <LinkBlockToolbar
203 setAttributes={ setAttributes }
204 htmlAttributes={ htmlAttributes }
205 tagName={ tagName }
206 context={ context }
207 />
208
209 <AlignmentToolbar
210 withTextAlign
211 getStyleValue={ getStyleValue }
212 onStyleChange={ onStyleChange }
213 setAttributes={ setAttributes }
214 clientId={ clientId }
215 />
216
217 { ! iconOnly && (
218 <DynamicTagBlockToolbar
219 value={ content }
220 tagName={ tagName }
221 setContentMode={ setContentMode }
222 contentMode={ contentMode }
223 isSelected={ isSelected }
224 onChange={ ( newValue ) => setAttributes( { content: newValue } ) }
225 context={ context }
226 />
227 ) }
228
229 <InspectorControls>
230 <StylesOnboarder />
231 <BlockStyles
232 settingsTab={ (
233 <BlockSettings
234 { ...props }
235 />
236 ) }
237 stylesTab={ (
238 <BlockStylesBuilder
239 attributes={ attributes }
240 setAttributes={ setAttributes }
241 shortcuts={ shortcuts }
242 onStyleChange={ onStyleChange }
243 name={ name }
244 />
245 ) }
246 />
247 </InspectorControls>
248
249 <RootElement
250 name={ name }
251 clientId={ clientId }
252 >
253 <>
254 { !! icon && (
255 <TagNameWithIcon { ...blockProps }>
256 { 'before' === iconLocation && ( <Icon icon={ icon } /> ) }
257 { ! iconOnly && (
258 renderContent( 'span' )
259 ) }
260 { 'after' === iconLocation && ( <Icon icon={ icon } /> ) }
261 </TagNameWithIcon>
262 ) }
263
264 { ! icon && (
265 renderContent( tagName || 'span', true )
266 ) }
267 </>
268 </RootElement>
269 </>
270 );
271 }
272
273 const Edit = compose(
274 withHtmlAttributes,
275 withStyles,
276 withDynamicTag,
277 withUniqueId
278 )( EditBlock );
279
280 export { Edit };
281