From 6ae6285600fee4c184a8f3adc7de87f4ca852a52 Mon Sep 17 00:00:00 2001 From: William Earnhardt Date: Mon, 19 Nov 2018 08:58:50 -0500 Subject: [PATCH] Recursively step through edits to track individually changed post meta (#10827) * Only send edited meta values as state to editPost * Recursively step through edits to granularly update state on EDIT_POST * Don't recurse for arrays * Editor: Deeply diff/merge edited property edits/updates * Editor: Pass meta updates from block as patch * Editor: Shallow merge on whitelisted post property edits --- .../editor/src/components/block-list/block.js | 7 +- packages/editor/src/store/reducer.js | 21 ++++- packages/editor/src/store/test/reducer.js | 84 +++++++++++++++++++ 3 files changed, 104 insertions(+), 8 deletions(-) diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index 614fbe9f762f0..1263bbe9e40bc 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -179,10 +179,7 @@ export class BlockListBlock extends Component { }, {} ); if ( size( metaAttributes ) ) { - this.props.onMetaChange( { - ...this.props.meta, - ...metaAttributes, - } ); + this.props.onMetaChange( metaAttributes ); } } @@ -646,7 +643,6 @@ const applyWithSelect = withSelect( ( select, { clientId, rootClientId, isLargeV isTyping, isCaretWithinFormattedText, getBlockIndex, - getEditedPostAttribute, getBlockMode, isSelectionEnabled, getSelectedBlocksInitialCaretPosition, @@ -673,7 +669,6 @@ const applyWithSelect = withSelect( ( select, { clientId, rootClientId, isLargeV isTypingWithinBlock: ( isSelected || isParentOfSelectedBlock ) && isTyping(), isCaretWithinFormattedText: isCaretWithinFormattedText(), order: getBlockIndex( clientId, rootClientId ), - meta: getEditedPostAttribute( 'meta' ), mode: getBlockMode( clientId ), isSelectionEnabled: isSelectionEnabled(), initialPosition: getSelectedBlocksInitialCaretPosition(), diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 6bfb468cd7157..5396186a9be44 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -35,6 +35,16 @@ import { } from './defaults'; import { insertAt, moveTo } from './array'; +/** + * Set of post properties for which edits should assume a merging behavior, + * assuming an object value. + * + * @type {Set} + */ +const EDIT_MERGE_PROPERTIES = new Set( [ + 'meta', +] ); + /** * Returns a post attribute value, flattening nested rendered content using its * raw value in place of its original object form. @@ -244,7 +254,14 @@ export const editor = flow( [ // Only assign into result if not already same value if ( value !== state[ key ] ) { result = getMutateSafeObject( state, result ); - result[ key ] = value; + + if ( EDIT_MERGE_PROPERTIES.has( key ) ) { + // Merge properties should assign to current value. + result[ key ] = { ...result[ key ], ...value }; + } else { + // Otherwise override. + result[ key ] = value; + } } return result; @@ -264,7 +281,7 @@ export const editor = flow( [ ( key ) => getPostRawValue( action.post[ key ] ); return reduce( state, ( result, value, key ) => { - if ( value !== getCanonicalValue( key ) ) { + if ( ! isEqual( value, getCanonicalValue( key ) ) ) { return result; } diff --git a/packages/editor/src/store/test/reducer.js b/packages/editor/src/store/test/reducer.js index d40b07b0b36bd..e088a2a67012a 100644 --- a/packages/editor/src/store/test/reducer.js +++ b/packages/editor/src/store/test/reducer.js @@ -1040,6 +1040,90 @@ describe( 'state', () => { } ); } ); + it( 'should merge object values', () => { + const original = editor( undefined, { + type: 'EDIT_POST', + edits: { + meta: { + a: 1, + }, + }, + } ); + + const state = editor( original, { + type: 'EDIT_POST', + edits: { + meta: { + b: 2, + }, + }, + } ); + + expect( state.present.edits ).toEqual( { + meta: { + a: 1, + b: 2, + }, + } ); + } ); + + it( 'return state by reference on unchanging update', () => { + const original = editor( undefined, {} ); + + const state = editor( original, { + type: 'UPDATE_POST', + edits: {}, + } ); + + expect( state.present.edits ).toBe( original.present.edits ); + } ); + + it( 'unset reset post values which match by canonical value', () => { + const original = editor( undefined, { + type: 'EDIT_POST', + edits: { + title: 'modified title', + }, + } ); + + const state = editor( original, { + type: 'RESET_POST', + post: { + title: { + raw: 'modified title', + }, + }, + } ); + + expect( state.present.edits ).toEqual( {} ); + } ); + + it( 'unset reset post values by deep match', () => { + const original = editor( undefined, { + type: 'EDIT_POST', + edits: { + title: 'modified title', + meta: { + a: 1, + b: 2, + }, + }, + } ); + + const state = editor( original, { + type: 'UPDATE_POST', + edits: { + title: 'modified title', + meta: { + a: 1, + b: 2, + }, + }, + } ); + + expect( state.present.edits ).toEqual( {} ); + } ); + it( 'should omit content when resetting', () => { // Use case: When editing in Text mode, we defer to content on // the property, but we reset blocks by parse when switching