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 ( -