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