-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Block Templates: Check the validity of the block list against the def…
…ined template (#5162)
- Loading branch information
1 parent
d189d54
commit d64de0f
Showing
16 changed files
with
549 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { every, map } from 'lodash'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { createBlock } from './factory'; | ||
|
||
/** | ||
* Checks whether a list of blocks matches a template by comparing the block names. | ||
* | ||
* @param {Array} blocks Block list. | ||
* @param {Array} template Block template. | ||
* | ||
* @return {boolean} Whether the list of blocks matches a templates | ||
*/ | ||
export function doBlocksMatchTemplate( blocks = [], template = [] ) { | ||
return ( | ||
blocks.length === template.length && | ||
every( template, ( [ name, , innerBlocksTemplate ], index ) => { | ||
const block = blocks[ index ]; | ||
return ( | ||
name === block.name && | ||
doBlocksMatchTemplate( block.innerBlocks, innerBlocksTemplate ) | ||
); | ||
} ) | ||
); | ||
} | ||
|
||
/** | ||
* Synchronize a block list with a block template. | ||
* | ||
* Synchronnizing a block list with a block template means that we loop over the blocks | ||
* keep the block as is if it matches the block at the same position in the template | ||
* (If it has the same name) and if doesn't match, we create a new block based on the template. | ||
* Extra blocks not present in the template are removed. | ||
* | ||
* @param {Array} blocks Block list. | ||
* @param {Array} template Block template. | ||
* | ||
* @return {Array} Updated Block list. | ||
*/ | ||
export function synchronizeBlocksWithTemplate( blocks = [], template = [] ) { | ||
return map( template, ( [ name, attributes, innerBlocksTemplate ], index ) => { | ||
const block = blocks[ index ]; | ||
|
||
if ( block && block.name === name ) { | ||
const innerBlocks = synchronizeBlocksWithTemplate( block.innerBlocks, innerBlocksTemplate ); | ||
return { ...block, innerBlocks }; | ||
} | ||
|
||
return createBlock( | ||
name, | ||
attributes, | ||
synchronizeBlocksWithTemplate( [], innerBlocksTemplate ) | ||
); | ||
} ); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { noop } from 'lodash'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { createBlock } from '../factory'; | ||
import { getBlockTypes, unregisterBlockType, registerBlockType } from '../registration'; | ||
import { doBlocksMatchTemplate, synchronizeBlocksWithTemplate } from '../templates'; | ||
|
||
describe( 'templates', () => { | ||
afterEach( () => { | ||
getBlockTypes().forEach( ( block ) => { | ||
unregisterBlockType( block.name ); | ||
} ); | ||
} ); | ||
|
||
beforeEach( () => { | ||
registerBlockType( 'core/test-block', { | ||
attributes: {}, | ||
save: noop, | ||
category: 'common', | ||
title: 'test block', | ||
} ); | ||
|
||
registerBlockType( 'core/test-block-2', { | ||
attributes: {}, | ||
save: noop, | ||
category: 'common', | ||
title: 'test block', | ||
} ); | ||
} ); | ||
|
||
describe( 'doBlocksMatchTemplate', () => { | ||
it( 'return true if for empty templates and blocks', () => { | ||
expect( doBlocksMatchTemplate() ).toBe( true ); | ||
} ); | ||
|
||
it( 'return true if the template matches the blocks', () => { | ||
const template = [ | ||
[ 'core/test-block' ], | ||
[ 'core/test-block-2' ], | ||
[ 'core/test-block-2' ], | ||
]; | ||
const blockList = [ | ||
createBlock( 'core/test-block' ), | ||
createBlock( 'core/test-block-2' ), | ||
createBlock( 'core/test-block-2' ), | ||
]; | ||
expect( doBlocksMatchTemplate( blockList, template ) ).toBe( true ); | ||
} ); | ||
|
||
it( 'return true if the template matches the blocks with nested blocks', () => { | ||
const template = [ | ||
[ 'core/test-block' ], | ||
[ 'core/test-block-2', {}, [ | ||
[ 'core/test-block' ], | ||
] ], | ||
[ 'core/test-block-2' ], | ||
]; | ||
const blockList = [ | ||
createBlock( 'core/test-block' ), | ||
createBlock( 'core/test-block-2', {}, [ createBlock( 'core/test-block' ) ] ), | ||
createBlock( 'core/test-block-2' ), | ||
]; | ||
expect( doBlocksMatchTemplate( blockList, template ) ).toBe( true ); | ||
} ); | ||
|
||
it( 'return false if the template length doesn\'t match the blocks length', () => { | ||
const template = [ | ||
[ 'core/test-block' ], | ||
[ 'core/test-block-2' ], | ||
]; | ||
const blockList = [ | ||
createBlock( 'core/test-block' ), | ||
createBlock( 'core/test-block-2' ), | ||
createBlock( 'core/test-block-2' ), | ||
]; | ||
expect( doBlocksMatchTemplate( blockList, template ) ).toBe( false ); | ||
} ); | ||
|
||
it( 'return false if the nested template doesn\'t match the blocks', () => { | ||
const template = [ | ||
[ 'core/test-block' ], | ||
[ 'core/test-block-2', {}, [ | ||
[ 'core/test-block' ], | ||
] ], | ||
[ 'core/test-block-2' ], | ||
]; | ||
const blockList = [ | ||
createBlock( 'core/test-block' ), | ||
createBlock( 'core/test-block-2', {}, [ createBlock( 'core/test-block-2' ) ] ), | ||
createBlock( 'core/test-block-2' ), | ||
]; | ||
expect( doBlocksMatchTemplate( blockList, template ) ).toBe( false ); | ||
} ); | ||
} ); | ||
|
||
describe( 'synchronizeBlocksWithTemplate', () => { | ||
it( 'should create blocks for each template entry', () => { | ||
const template = [ | ||
[ 'core/test-block' ], | ||
[ 'core/test-block-2' ], | ||
[ 'core/test-block-2' ], | ||
]; | ||
const blockList = []; | ||
expect( synchronizeBlocksWithTemplate( blockList, template ) ).toMatchObject( [ | ||
{ name: 'core/test-block' }, | ||
{ name: 'core/test-block-2' }, | ||
{ name: 'core/test-block-2' }, | ||
] ); | ||
} ); | ||
|
||
it( 'should create nested blocks', () => { | ||
const template = [ | ||
[ 'core/test-block', {}, [ | ||
[ 'core/test-block-2' ], | ||
] ], | ||
]; | ||
const blockList = []; | ||
expect( synchronizeBlocksWithTemplate( blockList, template ) ).toMatchObject( [ | ||
{ name: 'core/test-block', innerBlocks: [ | ||
{ name: 'core/test-block-2' }, | ||
] }, | ||
] ); | ||
} ); | ||
|
||
it( 'should append blocks if more blocks in the template', () => { | ||
const template = [ | ||
[ 'core/test-block' ], | ||
[ 'core/test-block-2' ], | ||
[ 'core/test-block-2' ], | ||
]; | ||
|
||
const block1 = createBlock( 'core/test-block' ); | ||
const block2 = createBlock( 'core/test-block-2' ); | ||
const blockList = [ block1, block2 ]; | ||
expect( synchronizeBlocksWithTemplate( blockList, template ) ).toMatchObject( [ | ||
block1, | ||
block2, | ||
{ name: 'core/test-block-2' }, | ||
] ); | ||
} ); | ||
|
||
it( 'should replace blocks if not matching blocks are found', () => { | ||
const template = [ | ||
[ 'core/test-block' ], | ||
[ 'core/test-block-2' ], | ||
[ 'core/test-block-2' ], | ||
]; | ||
|
||
const block1 = createBlock( 'core/test-block' ); | ||
const block2 = createBlock( 'core/test-block' ); | ||
const blockList = [ block1, block2 ]; | ||
expect( synchronizeBlocksWithTemplate( blockList, template ) ).toMatchObject( [ | ||
block1, | ||
{ name: 'core/test-block-2' }, | ||
{ name: 'core/test-block-2' }, | ||
] ); | ||
} ); | ||
|
||
it( 'should remove blocks if extra blocks are found', () => { | ||
const template = [ | ||
[ 'core/test-block' ], | ||
]; | ||
|
||
const block1 = createBlock( 'core/test-block' ); | ||
const block2 = createBlock( 'core/test-block' ); | ||
const blockList = [ block1, block2 ]; | ||
expect( synchronizeBlocksWithTemplate( blockList, template ) ).toEqual( [ | ||
block1, | ||
] ); | ||
} ); | ||
} ); | ||
} ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { connect } from 'react-redux'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Notice, Button } from '@wordpress/components'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import './style.scss'; | ||
import { isValidTemplate } from '../../store/selectors'; | ||
import { setTemplateValidity, synchronizeTemplate } from '../../store/actions'; | ||
|
||
function TemplateValidationNotice( { isValid, ...props } ) { | ||
if ( isValid ) { | ||
return null; | ||
} | ||
|
||
const confirmSynchronization = () => { | ||
// eslint-disable-next-line no-alert | ||
if ( window.confirm( __( 'Resetting the template may result in loss of content, do you want to continue?' ) ) ) { | ||
props.synchronizeTemplate(); | ||
} | ||
}; | ||
|
||
return ( | ||
<Notice className="editor-template-validation-notice" isDismissible={ false } status="warning"> | ||
<p>{ __( 'The content of your post doesn\'t match the template assigned to your post type.' ) }</p> | ||
<div> | ||
<Button className="button" onClick={ props.resetTemplateValidity }>{ __( 'Keep it as is' ) }</Button> | ||
<Button onClick={ confirmSynchronization } isPrimary>{ __( 'Reset the template' ) }</Button> | ||
</div> | ||
</Notice> | ||
); | ||
} | ||
|
||
export default connect( | ||
( state ) => ( { | ||
isValid: isValidTemplate( state ), | ||
} ), | ||
{ | ||
resetTemplateValidity: () => setTemplateValidity( true ), | ||
synchronizeTemplate, | ||
} | ||
)( TemplateValidationNotice ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.editor-template-validation-notice { | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
|
||
.components-button { | ||
margin-left: 5px; | ||
} | ||
} |
Oops, something went wrong.