diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 47ab161e05723..c9e744157c65b 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -27,7 +27,6 @@ "@wordpress/i18n": "file:../i18n", "@wordpress/rich-text": "file:../rich-text", "lodash": "^4.17.11", - "memize": "^1.0.5", "rememo": "^3.0.0", "uuid": "^3.3.2" }, diff --git a/packages/annotations/src/format/annotation.js b/packages/annotations/src/format/annotation.js index e72786bad0e3b..a2f6f2973c726 100644 --- a/packages/annotations/src/format/annotation.js +++ b/packages/annotations/src/format/annotation.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import memize from 'memize'; - /** * WordPress dependencies */ @@ -120,40 +115,6 @@ function updateAnnotationsWithPositions( annotations, positions, { removeAnnotat } ); } -/** - * Create prepareEditableTree memoized based on the annotation props. - * - * @param {Object} The props with annotations in them. - * - * @return {Function} The prepareEditableTree. - */ -const createPrepareEditableTree = memize( ( props ) => { - const { annotations } = props; - - return ( formats, text ) => { - if ( annotations.length === 0 ) { - return formats; - } - - let record = { formats, text }; - record = applyAnnotations( record, annotations ); - return record.formats; - }; -} ); - -/** - * Returns the annotations as a props object. Memoized to prevent re-renders. - * - * @param {Array} The annotations to put in the object. - * - * @return {Object} The annotations props object. - */ -const getAnnotationObject = memize( ( annotations ) => { - return { - annotations, - }; -} ); - export const annotation = { name: FORMAT_NAME, title: __( 'Annotation' ), @@ -167,9 +128,21 @@ export const annotation = { return null; }, __experimentalGetPropsForEditableTreePreparation( select, { richTextIdentifier, blockClientId } ) { - return getAnnotationObject( select( STORE_KEY ).__experimentalGetAnnotationsForRichText( blockClientId, richTextIdentifier ) ); + return { + annotations: select( STORE_KEY ).__experimentalGetAnnotationsForRichText( blockClientId, richTextIdentifier ), + }; + }, + __experimentalCreatePrepareEditableTree( { annotations } ) { + return ( formats, text ) => { + if ( annotations.length === 0 ) { + return formats; + } + + let record = { formats, text }; + record = applyAnnotations( record, annotations ); + return record.formats; + }; }, - __experimentalCreatePrepareEditableTree: createPrepareEditableTree, __experimentalGetPropsForEditableTreeChangeHandler( dispatch ) { return { removeAnnotation: dispatch( STORE_KEY ).__experimentalRemoveAnnotation, diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index ed94653f98be3..1227dd23d4b54 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -8,8 +8,6 @@ import { isEqual, omit, pickBy, - get, - isPlainObject, } from 'lodash'; import memize from 'memize'; @@ -48,6 +46,7 @@ import { import { decodeEntities } from '@wordpress/html-entities'; import { withFilters, IsolatedEventContainer } from '@wordpress/components'; import deprecated from '@wordpress/deprecated'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -91,6 +90,20 @@ const globalStyle = document.createElement( 'style' ); document.head.appendChild( globalStyle ); +function createPrepareEditableTree( props ) { + const fns = Object.keys( props ).reduce( ( accumulator, key ) => { + if ( key.startsWith( 'format_prepare_functions' ) ) { + accumulator.push( props[ key ] ); + } + + return accumulator; + }, [] ); + + return ( value ) => fns.reduce( ( accumulator, fn ) => { + return fn( accumulator, value.text ); + }, value.formats ); +} + export class RichText extends Component { constructor( { value, onReplace, multiline } ) { super( ...arguments ); @@ -202,7 +215,7 @@ export class RichText extends Component { range, multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, - prepareEditableTree: this.props.prepareEditableTree, + prepareEditableTree: createPrepareEditableTree( this.props ), __unstableIsEditableTree: true, } ); } @@ -213,7 +226,7 @@ export class RichText extends Component { current: this.editableRef, multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, - prepareEditableTree: this.props.prepareEditableTree, + prepareEditableTree: createPrepareEditableTree( this.props ), __unstableDomOnly: domOnly, } ); } @@ -484,18 +497,6 @@ export class RichText extends Component { } } - /** - * Calls all registered onChangeEditableValue handlers. - * - * @param {Array} formats The formats of the latest rich-text value. - * @param {string} text The text of the latest rich-text value. - */ - onChangeEditableValue( { formats, text } ) { - get( this.props, [ 'onChangeEditableValue' ], [] ).forEach( ( eventHandler ) => { - eventHandler( formats, text ); - } ); - } - /** * Sync the value to global state. The node tree and selection will also be * updated if differences are found. @@ -509,8 +510,14 @@ export class RichText extends Component { this.applyRecord( record ); const { start, end, activeFormats = [] } = record; + const changeHandlers = pickBy( this.props, ( v, key ) => + key.startsWith( 'format_on_change_functions_' ) + ); + + Object.values( changeHandlers ).forEach( ( changeHandler ) => { + changeHandler( record.formats, record.text ); + } ); - this.onChangeEditableValue( record ); this.savedContent = this.valueToFormat( record ); this.props.onChange( this.savedContent ); this.setState( { start, end, activeFormats } ); @@ -912,23 +919,13 @@ export class RichText extends Component { this.savedContent = value; } - // If any format props update, reapply value. - const shouldReapply = Object.keys( this.props ).some( ( name ) => { - if ( name.indexOf( 'format_' ) !== 0 ) { - return false; - } - - // Allow primitives and arrays: - if ( ! isPlainObject( this.props[ name ] ) ) { - return this.props[ name ] !== prevProps[ name ]; - } - - return Object.keys( this.props[ name ] ).some( ( subName ) => { - return this.props[ name ][ subName ] !== prevProps[ name ][ subName ]; - } ); - } ); + const prefix = 'format_prepare_props_'; + const predicate = ( v, key ) => key.startsWith( prefix ); + const prepareProps = pickBy( this.props, predicate ); + const prevPrepareProps = pickBy( prevProps, predicate ); - if ( shouldReapply ) { + // If any format prepare props update, reapply value. + if ( ! isShallowEqual( prepareProps, prevPrepareProps ) ) { const record = this.formatToValue( value ); // Maintain the previous selection if the instance is currently @@ -942,15 +939,6 @@ export class RichText extends Component { } } - /** - * Get props that are provided by formats to modify RichText. - * - * @return {Object} Props that start with 'format_'. - */ - getFormatProps() { - return pickBy( this.props, ( propValue, name ) => name.startsWith( 'format_' ) ); - } - /** * Converts the outside data structure to our internal representation. * @@ -988,7 +976,7 @@ export class RichText extends Component { return unstableToDom( { value, multilineTag: this.multilineTag, - prepareEditableTree: this.props.prepareEditableTree, + prepareEditableTree: createPrepareEditableTree( this.props ), } ).body.innerHTML; } @@ -1066,9 +1054,7 @@ export class RichText extends Component { const record = this.getRecord(); return ( -
+
{ isSelected && this.multilineTag === 'li' && ( { - return [ - ...previousStack, - newFunction, - ]; - } ); - - if ( - settings.__experimentalCreatePrepareEditableTree - ) { + if ( settings.__experimentalCreatePrepareEditableTree ) { addFilter( 'experimentalRichText', name, ( OriginalComponent ) => { - let Component = OriginalComponent; - if ( - settings.__experimentalCreatePrepareEditableTree || - settings.__experimentalCreateFormatToValue || - settings.__experimentalCreateValueToFormat - ) { - Component = ( props ) => { - const additionalProps = {}; - - if ( settings.__experimentalCreatePrepareEditableTree ) { - additionalProps.prepareEditableTree = getFunctionStackMemoized( - props.prepareEditableTree, - settings.__experimentalCreatePrepareEditableTree( props[ `format_${ name }` ], { - richTextIdentifier: props.identifier, - blockClientId: props.clientId, - } ) - ); + const selectPrefix = `format_prepare_props_(${ name })_`; + const dispatchPrefix = `format_on_change_props_(${ name })_`; + + const Component = ( props ) => { + const newProps = { ...props }; + const propsByPrefix = Object.keys( props ).reduce( ( accumulator, key ) => { + if ( key.startsWith( selectPrefix ) ) { + accumulator[ key.slice( selectPrefix.length ) ] = props[ key ]; } - if ( settings.__experimentalCreateOnChangeEditableValue ) { - const dispatchProps = Object.keys( props ).reduce( ( accumulator, propKey ) => { - const propValue = props[ propKey ]; - const keyPrefix = `format_${ name }_dispatch_`; - if ( propKey.startsWith( keyPrefix ) ) { - const realKey = propKey.replace( keyPrefix, '' ); - - accumulator[ realKey ] = propValue; - } - - return accumulator; - }, {} ); - - additionalProps.onChangeEditableValue = getFunctionStackMemoized( - props.onChangeEditableValue, - settings.__experimentalCreateOnChangeEditableValue( { - ...props[ `format_${ name }` ], - ...dispatchProps, - }, { - richTextIdentifier: props.identifier, - blockClientId: props.clientId, - } ) - ); + if ( key.startsWith( dispatchPrefix ) ) { + accumulator[ key.slice( dispatchPrefix.length ) ] = props[ key ]; } - return ; + return accumulator; + }, {} ); + const args = { + richTextIdentifier: props.identifier, + blockClientId: props.clientId, }; - } + + newProps[ `format_prepare_functions_(${ name })` ] = + settings.__experimentalCreatePrepareEditableTree( + propsByPrefix, + args + ); + + if ( settings.__experimentalCreateOnChangeEditableValue ) { + newProps[ `format_on_change_functions_(${ name })` ] = + settings.__experimentalCreateOnChangeEditableValue( + propsByPrefix, + args + ); + } + + return ; + }; const hocs = []; if ( settings.__experimentalGetPropsForEditableTreePreparation ) { - hocs.push( withSelect( ( sel, { clientId, identifier } ) => ( { - [ `format_${ name }` ]: settings.__experimentalGetPropsForEditableTreePreparation( - sel, - { + hocs.push( withSelect( ( sel, { clientId, identifier } ) => + mapKeys( + settings.__experimentalGetPropsForEditableTreePreparation( sel, { richTextIdentifier: identifier, blockClientId: clientId, - } - ), - } ) ) ); + } ), + ( value, key ) => selectPrefix + key + ) + ) ); } if ( settings.__experimentalGetPropsForEditableTreeChangeHandler ) { - hocs.push( withDispatch( ( disp, { clientId, identifier } ) => { - const dispatchProps = settings.__experimentalGetPropsForEditableTreeChangeHandler( - disp, - { + hocs.push( withDispatch( ( disp, { clientId, identifier } ) => + mapKeys( + settings.__experimentalGetPropsForEditableTreeChangeHandler( disp, { richTextIdentifier: identifier, blockClientId: clientId, - } - ); - - return mapKeys( dispatchProps, ( value, key ) => { - return `format_${ name }_dispatch_${ key }`; - } ); - } ) ); + } ), + ( value, key ) => dispatchPrefix + key + ) + ) ); } - return compose( hocs )( Component ); + return hocs.length ? compose( hocs )( Component ) : Component; } ); } diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index 9ebe8365a25ba..33cc08b27e47d 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -113,12 +113,6 @@ function remove( node ) { return node.parentNode.removeChild( node ); } -function prepareFormats( prepareEditableTree = [], value ) { - return prepareEditableTree.reduce( ( accumlator, fn ) => { - return fn( accumlator, value.text ); - }, value.formats ); -} - export function toDom( { value, multilineTag, @@ -128,11 +122,15 @@ export function toDom( { let startPath = []; let endPath = []; - const tree = toTree( { - value: { + if ( prepareEditableTree ) { + value = { ...value, - formats: prepareFormats( prepareEditableTree, value ), - }, + formats: prepareEditableTree( value ), + }; + } + + const tree = toTree( { + value, multilineTag, createEmpty, append, diff --git a/packages/rich-text/src/unregister-format-type.js b/packages/rich-text/src/unregister-format-type.js index 6f6741183ee16..a70c1d1d417f2 100644 --- a/packages/rich-text/src/unregister-format-type.js +++ b/packages/rich-text/src/unregister-format-type.js @@ -22,10 +22,7 @@ export function unregisterFormatType( name ) { return; } - if ( - oldFormat.__experimentalCreatePrepareEditableTree && - oldFormat.__experimentalGetPropsForEditableTreePreparation - ) { + if ( oldFormat.__experimentalCreatePrepareEditableTree ) { removeFilter( 'experimentalRichText', name ); }