PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 2.4.9
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v2.4.9
3.4.4 3.4.3 3.4.2 3.4.1 3.4.0 3.3.9 3.3.8 3.3.7 3.3.6 3.3.5 3.3.4 3.3.3 3.3.2 3.3.1 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 2.2.9 2.3.0 All 197 releases
convertkit / resources / backend / js / gutenberg-block-formatters.js

gutenberg-block-formatters.js in Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages 2.4.9, at resources/backend/js/gutenberg-block-formatters.js

359 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Registers formatters in the Block Toolbar in the Gutenberg editor.
3 *
4 * @since 2.2.0
5 *
6 * @package ConvertKit
7 * @author ConvertKit
8 */
9
10 // Register Gutenberg Block Toolbar formatters if the Gutenberg Editor is loaded on screen.
11 // This prevents JS errors if this script is accidentally enqueued on a non-
12 // Gutenberg editor screen, or the Classic Editor Plugin is active.
13 if ( typeof wp !== 'undefined' &&
14 typeof wp.blocks !== 'undefined' ) {
15
16 // Register each ConvertKit formatter in Gutenberg.
17 for ( const formatter in convertkit_block_formatters ) {
18 convertKitGutenbergRegisterBlockFormatter( convertkit_block_formatters[ formatter ] );
19 }
20
21 }
22
23 /**
24 * Registers the given formatter in Gutenberg.
25 *
26 * @since 2.2.0
27 *
28 * @param object formatter Block formatter.
29 */
30 function convertKitGutenbergRegisterBlockFormatter( formatter ) {
31
32 ( function ( editor, richText, element, components ) {
33
34 // Define the Gutenberg/React components to use.
35 const {
36 Fragment,
37 useState,
38 createElement
39 } = element;
40 const {
41 registerFormatType,
42 toggleFormat,
43 applyFormat,
44 useAnchorRef,
45 useAnchor
46 } = richText;
47 const {
48 BlockControls,
49 RichTextToolbarButton,
50 } = editor;
51 const {
52 Button,
53 Popover,
54 SelectControl
55 } = components;
56
57 /**
58 * Returns the icon to display in the block toolbar for this formatter, depending
59 * on the supplied formatter's configuration.
60 *
61 * @since 2.2.0
62 *
63 * @return element|string
64 */
65 const getIcon = function () {
66
67 // Return a fallback default icon if none is specified for this block formatter.
68 if ( typeof formatter.gutenberg_icon === 'undefined' ) {
69 return 'dashicons-tablet';
70 }
71
72 // Return HTML element if the icon is an SVG string.
73 if ( formatter.gutenberg_icon.search( 'svg' ) >= 0 ) {
74 return element.RawHTML(
75 {
76 children: formatter.gutenberg_icon
77 }
78 );
79 }
80
81 // Just return the string, as it's a dashicon CSS class.
82 return formatter.gutenberg_icon;
83
84 }
85
86 /**
87 * Returns an object of this formatter's attributes if this formatter
88 * has been used on the selected text.
89 *
90 * Returns blank attributes if this formatter has not been used on the
91 * selected text.
92 *
93 * @since 2.2.0
94 *
95 * @param object activeFormats All active formatters applied to the selected text.
96 * @return object
97 */
98 const getAttributes = function ( activeFormats ) {
99
100 // Define the attribute object.
101 let attributes = {};
102 for ( let attribute in formatter.attributes ) {
103 attributes[ attribute ] = '';
104 }
105
106 // Return if no active formats have been applied to the selected text.
107 if ( typeof activeFormats === 'undefined' ) {
108 return attributes;
109 }
110
111 // Return if this formatter has not been used on the selected text.
112 const formats = activeFormats.filter( format => 'convertkit/' + formatter.name === format['type'] );
113 if ( formats.length === 0 ) {
114 return attributes;
115 }
116
117 // This formatter has been applied to the selected text.
118 // Build the object of attributes and return.
119 for ( let attribute in formatter.attributes ) {
120 if ( typeof formats[0].unregisteredAttributes !== 'undefined' ) {
121 attributes[ attribute ] = formats[0].unregisteredAttributes[ attribute ];
122 } else if ( typeof formats[0].attributes !== 'undefined' ) {
123 attributes[ attribute ] = formats[0].attributes[ attribute ];
124 }
125 }
126
127 return attributes;
128
129 }
130
131 /**
132 * Updates the block formatter's attributes when a field in the
133 * formatter's popover modal has its value changed.
134 *
135 * @since 2.2.0
136 *
137 * @param object props Block formatter properties.
138 * @param array field Field definition.
139 * @param string newValue New value
140 */
141 const setAttributes = function ( props, field, newValue ) {
142
143 // Define properties and functions to use.
144 const { onChange, value } = props;
145
146 // If no value exists, remove the formatter.
147 if ( newValue === '' ) {
148 return onChange(
149 toggleFormat(
150 value,
151 {
152 type: 'convertkit/' + formatter.name
153 }
154 )
155 );
156 }
157
158 // Build object of new attributes.
159 let attributes = {};
160 for ( let attribute in formatter.attributes ) {
161 // If 'None' selected, blank the attribute's value.
162 if ( newValue === '' ) {
163 attributes[ attribute ] = '';
164 } else {
165 attributes[ attribute ] = field.data[ newValue ][ attribute ];
166 }
167 }
168
169 // Apply formatter with new attributes.
170 return onChange(
171 applyFormat(
172 value,
173 {
174 type: 'convertkit/' + formatter.name,
175 attributes: attributes
176 }
177 )
178 );
179
180 }
181
182 /**
183 * Return an array of field elements to display in the popover modal when
184 * this formatter is active.
185 *
186 * @since 2.2.0
187 *
188 * @param object props Block formatter properties.
189 * @param object setShowPopover Function to toggle showing/hiding the popover.
190 * @param object attributes Field attributes.
191 * @return array Field elements
192 */
193 const getFields = function ( props, setShowPopover, attributes ) {
194
195 // Define array of field elements.
196 let elements = [];
197
198 // Return if no fields exist.
199 if ( formatter.fields.length === 0 ) {
200 return elements;
201 }
202
203 // Iterate through the formatter's fields, adding a field element for each.
204 for ( let fieldName in formatter.fields ) {
205 const field = formatter.fields[ fieldName ];
206
207 // Build options for <select> input.
208 let fieldOptions = [
209 {
210 label: '(None)',
211 value: ''
212 }
213 ];
214 for ( let fieldValue in field.values ) {
215 fieldOptions.push(
216 {
217 label: field.values[ fieldValue ],
218 value: fieldValue
219 }
220 );
221 }
222
223 // Sort field's options alphabetically by label.
224 fieldOptions.sort(
225 function ( x, y ) {
226
227 let a = x.label.toUpperCase(),
228 b = y.label.toUpperCase();
229 return a.localeCompare( b );
230
231 }
232 );
233
234 // Add field to array.
235 elements.push(
236 createElement(
237 SelectControl,
238 {
239 key: 'convertkit_' + formatter.name + '_' + fieldName,
240 id: 'convertkit-' + formatter.name + '-' + fieldName,
241 label: field.label,
242 value: attributes[ fieldName ],
243 help: field.description,
244 options: fieldOptions,
245 onChange: function ( newValue ) {
246
247 // Hide popover.
248 setShowPopover( false );
249
250 // Update and apply attributes to the selected text.
251 setAttributes( props, field, newValue );
252
253 }
254 }
255 )
256 );
257 }
258
259 // Return elements.
260 return elements;
261
262 }
263
264 /**
265 * Display modal when the formatter's button is clicked, and save
266 * changes that are made.
267 *
268 * @since 2.2.0
269 *
270 * @param object props Block formatter properties.
271 * @return object Block formatter button and modal elements
272 */
273 const editFormatType = function ( props ) {
274
275 // Get props.
276 const { contentRef, isActive, value } = props;
277 const { activeFormats } = value;
278 let anchorRef;
279
280 // Get anchor reference to the text.
281 if ( typeof useAnchor === 'undefined' ) {
282 // Use WordPress 6.0 and lower useAnchorRef(), as useAnchor() isn't available.
283 anchorRef = useAnchorRef( { ref: contentRef, value } );
284 } else {
285 // Use WordPress 6.1+ useAnchor(), as useAnchorRef() is deprecated in 6.2+.
286 anchorRef = useAnchor( { editableContentElement: contentRef.current, value } );
287 }
288
289 // State to show popover.
290 const [ showPopover, setShowPopover ] = useState( false );
291
292 // Get attributes.
293 let attributes = getAttributes( activeFormats );
294
295 // Define fields to display in the popover modal.
296 let popoverModalElements = getFields( props, setShowPopover, attributes );
297
298 // Return block toolbar button and its modal.
299 return (
300 createElement(
301 Fragment,
302 {
303 key: 'convertkit_' + formatter.name + '_rich_text_toolbar_fragment'
304 },
305 // Register the button in the rich text toolbar.
306 createElement(
307 RichTextToolbarButton,
308 {
309 key: 'convertkit_' + formatter.name + '_rich_text_toolbar_button',
310 icon: getIcon( formatter ),
311 title: formatter.title,
312 isActive: isActive,
313 onClick: function () {
314 setShowPopover( true );
315 }
316 },
317 ),
318 // Popover which displays fields when the button is active.
319 showPopover && ( createElement(
320 Popover,
321 {
322 key: 'convertkit_' + formatter.name + '_popover',
323 className: 'convertkit-popover',
324 anchor: anchorRef,
325 onClose: function () {
326 setShowPopover( false );
327 }
328 },
329 popoverModalElements
330 ) )
331 )
332 );
333
334 }
335
336 // Register Format Type.
337 registerFormatType(
338 'convertkit/' + formatter.name,
339 {
340 title: formatter.title,
341
342 // The tagName and className combination allow Gutenberg to uniquely identify
343 // whether this formatter has been used on the selected text.
344 tagName: formatter.tag,
345 className: 'convertkit-' + formatter.name,
346 attributes: formatter.attributes,
347 edit: editFormatType,
348 }
349 );
350
351 } (
352 window.wp.blockEditor,
353 window.wp.richText,
354 window.wp.element,
355 window.wp.components
356 ) );
357
358 }
359