Skip to content

Commit 88112df

Browse files
noisysocksaduth
authored andcommitted
Prevent RESET_BLOCKS from affecting reusable blocks (#11746)
* Prevent RESET_BLOCKS from affecting reusable blocks Changes the editor reducer so that RESET_BLOCKS will only remove blocks that are actually in the post, i.e. blocks that are a descendent of the `''` `blocks.order` key. This fixes a bug where editing a post in Text Mode would break any reusable blocks in the post. * Augment RESET_BLOCKS action instead of replacing it Keep the RESET_BLOCKS logic in the reducer by instead using a higher-order reducer to augment RESET_BLOCKS with a list of client IDs that should remain in state as they are referenced by a reusable block. * Revert "Augment RESET_BLOCKS action instead of replacing it" This reverts commit 7d3d116. * Use reduce() to simplify getNestedBlockClientIds()
1 parent e601299 commit 88112df

File tree

2 files changed

+105
-2
lines changed

2 files changed

+105
-2
lines changed

packages/editor/src/store/reducer.js

+53-2
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,28 @@ function getFlattenedBlocks( blocks ) {
111111
return flattenedBlocks;
112112
}
113113

114+
/**
115+
* Given a block order map object, returns *all* of the block client IDs that are
116+
* a descendant of the given root client ID.
117+
*
118+
* Calling this with `rootClientId` set to `''` results in a list of client IDs
119+
* that are in the post. That is, it excludes blocks like fetched reusable
120+
* blocks which are stored into state but not visible.
121+
*
122+
* @param {Object} blocksOrder Object that maps block client IDs to a list of
123+
* nested block client IDs.
124+
* @param {?string} rootClientId The root client ID to search. Defaults to ''.
125+
*
126+
* @return {Array} List of descendant client IDs.
127+
*/
128+
function getNestedBlockClientIds( blocksOrder, rootClientId = '' ) {
129+
return reduce( blocksOrder[ rootClientId ], ( result, clientId ) => [
130+
...result,
131+
clientId,
132+
...getNestedBlockClientIds( blocksOrder, clientId ),
133+
], [] );
134+
}
135+
114136
/**
115137
* Returns an object against which it is safe to perform mutating operations,
116138
* given the original object and its current working copy.
@@ -221,6 +243,35 @@ const withInnerBlocksRemoveCascade = ( reducer ) => ( state, action ) => {
221243
return reducer( state, action );
222244
};
223245

246+
/**
247+
* Higher-order reducer which targets the combined blocks reducer and handles
248+
* the `RESET_BLOCKS` action. When dispatched, this action will replace all
249+
* blocks that exist in the post, leaving blocks that exist only in state (e.g.
250+
* reusable blocks) alone.
251+
*
252+
* @param {Function} reducer Original reducer function.
253+
*
254+
* @return {Function} Enhanced reducer function.
255+
*/
256+
const withBlockReset = ( reducer ) => ( state, action ) => {
257+
if ( state && action.type === 'RESET_BLOCKS' ) {
258+
const visibleClientIds = getNestedBlockClientIds( state.order );
259+
return {
260+
...state,
261+
byClientId: {
262+
...omit( state.byClientId, visibleClientIds ),
263+
...getFlattenedBlocks( action.blocks ),
264+
},
265+
order: {
266+
...omit( state.order, visibleClientIds ),
267+
...mapBlockOrder( action.blocks ),
268+
},
269+
};
270+
}
271+
272+
return reducer( state, action );
273+
};
274+
224275
/**
225276
* Undoable reducer returning the editor post state, including blocks parsed
226277
* from current HTML markup.
@@ -297,6 +348,8 @@ export const editor = flow( [
297348
blocks: flow( [
298349
combineReducers,
299350

351+
withBlockReset,
352+
300353
// Track whether changes exist, resetting at each post save. Relies on
301354
// editor initialization firing post reset as an effect.
302355
withChangeDetection( {
@@ -306,7 +359,6 @@ export const editor = flow( [
306359
] )( {
307360
byClientId( state = {}, action ) {
308361
switch ( action.type ) {
309-
case 'RESET_BLOCKS':
310362
case 'SETUP_EDITOR_STATE':
311363
return getFlattenedBlocks( action.blocks );
312364

@@ -409,7 +461,6 @@ export const editor = flow( [
409461

410462
order( state = {}, action ) {
411463
switch ( action.type ) {
412-
case 'RESET_BLOCKS':
413464
case 'SETUP_EDITOR_STATE':
414465
return mapBlockOrder( action.blocks );
415466

packages/editor/src/store/test/reducer.js

+52
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,58 @@ describe( 'state', () => {
11581158
} );
11591159

11601160
describe( 'blocks', () => {
1161+
it( 'should not reset any blocks that are not in the post', () => {
1162+
const actions = [
1163+
{
1164+
type: 'RESET_BLOCKS',
1165+
blocks: [
1166+
{
1167+
clientId: 'block1',
1168+
innerBlocks: [
1169+
{ clientId: 'block11', innerBlocks: [] },
1170+
{ clientId: 'block12', innerBlocks: [] },
1171+
],
1172+
},
1173+
],
1174+
},
1175+
{
1176+
type: 'RECEIVE_BLOCKS',
1177+
blocks: [
1178+
{
1179+
clientId: 'block2',
1180+
innerBlocks: [
1181+
{ clientId: 'block21', innerBlocks: [] },
1182+
{ clientId: 'block22', innerBlocks: [] },
1183+
],
1184+
},
1185+
],
1186+
},
1187+
];
1188+
const original = deepFreeze( actions.reduce( editor, undefined ) );
1189+
1190+
const state = editor( original, {
1191+
type: 'RESET_BLOCKS',
1192+
blocks: [
1193+
{
1194+
clientId: 'block3',
1195+
innerBlocks: [
1196+
{ clientId: 'block31', innerBlocks: [] },
1197+
{ clientId: 'block32', innerBlocks: [] },
1198+
],
1199+
},
1200+
],
1201+
} );
1202+
1203+
expect( state.present.blocks.byClientId ).toEqual( {
1204+
block2: { clientId: 'block2' },
1205+
block21: { clientId: 'block21' },
1206+
block22: { clientId: 'block22' },
1207+
block3: { clientId: 'block3' },
1208+
block31: { clientId: 'block31' },
1209+
block32: { clientId: 'block32' },
1210+
} );
1211+
} );
1212+
11611213
describe( 'byClientId', () => {
11621214
it( 'should return with attribute block updates', () => {
11631215
const original = deepFreeze( editor( undefined, {

0 commit comments

Comments
 (0)