Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Editor: Consider single unmodified default block as empty content
  • Loading branch information
aduth committed Sep 17, 2018
commit 4b4b014c429315d5a1f39b8cb1fd97ec03c68737
1 change: 1 addition & 0 deletions packages/editor/src/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ import './custom-class-name';
import './default-autocompleters';
import './generated-class-name';
import './layout';
import './process-content';
91 changes: 91 additions & 0 deletions packages/editor/src/hooks/process-content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* WordPress dependencies
*/
import { addFilter } from '@wordpress/hooks';
import { removep } from '@wordpress/autop';
import {
isUnmodifiedDefaultBlock,
getUnknownTypeHandlerName,
} from '@wordpress/blocks';

/**
* Returns true if the given array of blocks consists of a single block of the
* unknown type, or false otherwise.
*
* @param {WPBlock[]} blocks Array of block objects.
*
* @return {boolean} Whether array consists of single unknown block.
*/
export function isSingleUnknownBlock( blocks ) {
return (
blocks.length === 1 &&
blocks[ 0 ].name === getUnknownTypeHandlerName()
);
}

/**
* Returns true if the given array of blocks consists of a single unmodified
* default block, or false otherwise.
*
* @param {WPBlock[]} blocks Array of block objects.
*
* @return {boolean} Whether array consists of single unmodified default block.
*/
export function isSingleUnmodifiedDefaultBlock( blocks ) {
return (
blocks.length === 1 &&
isUnmodifiedDefaultBlock( blocks[ 0 ] )
);
}

/**
* Given an array of blocks, returns either an empty array if the blocks
* consist only of a single unmodified default block. Otherwise returns the
* original array unmodified.
*
* @param {WPBlock[]} blocks Array of blocks.
*
* @return {WPBlock[]} Empty array, or original passed set of blocks.
*/
export function omitSingleUnmodifiedDefaultBlock( blocks ) {
if ( isSingleUnmodifiedDefaultBlock( blocks ) ) {
blocks = [];
}

return blocks;
}

/**
* Given an HTML string and array of blocks, returns a formatted HTML string
* with paragraph tags removed via `removep` behavior if the array of blocks
* consists only of a single block of the unknown type.
*
* @link https://www.npmjs.com/package/@wordpress/autop
*
* @param {string} content HTML content.
* @param {WPBlock[]} blocks Array of blocks from which HTML content has been
* generated.
*
* @return {string} HTML content, with `removep` filtering applied if the array
* of blocks from which it was generated consists only of a
* single block of the unknown type.
*/
export function removepSingleUnknownBlock( content, blocks ) {
if ( isSingleUnknownBlock( blocks ) ) {
content = removep( content );
}

return content;
}

addFilter(
'editor.selectors.getBlockContent',
'core/processContent/removepSingleUnknownBlock',
removepSingleUnknownBlock
);

addFilter(
'editor.selectors.getBlocksForSave',
'core/processContent/omitSingleUnmodifiedDefaultBlock',
omitSingleUnmodifiedDefaultBlock
);
179 changes: 179 additions & 0 deletions packages/editor/src/hooks/test/process-content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/**
* WordPress dependencies
*/
import {
getBlockTypes,
unregisterBlockType,
registerBlockType,
createBlock,
getDefaultBlockName,
setDefaultBlockName,
getUnknownTypeHandlerName,
setUnknownTypeHandlerName,
} from '@wordpress/blocks';

/**
* Internal dependencies
*/
import {
isSingleUnknownBlock,
isSingleUnmodifiedDefaultBlock,
omitSingleUnmodifiedDefaultBlock,
removepSingleUnknownBlock,
} from '../process-content';

describe( 'processContent', () => {
let originalDefaultBlockName, originalUnknownTypeHandlerName;

beforeAll( () => {
originalDefaultBlockName = getDefaultBlockName();
originalUnknownTypeHandlerName = getUnknownTypeHandlerName();

registerBlockType( 'core/default', {
category: 'common',
title: 'default',
attributes: {
modified: {
type: 'boolean',
default: false,
},
},
save: () => null,
} );
registerBlockType( 'core/unknown', {
category: 'common',
title: 'unknown',
save: () => null,
} );
setDefaultBlockName( 'core/default' );
setUnknownTypeHandlerName( 'core/unknown' );
} );

afterAll( () => {
setDefaultBlockName( originalDefaultBlockName );
setUnknownTypeHandlerName( originalUnknownTypeHandlerName );
getBlockTypes().forEach( ( block ) => {
unregisterBlockType( block.name );
} );
} );

describe( 'isSingleUnknownBlock', () => {
it( 'returns false if multiple blocks passed', () => {
const blocks = [
createBlock( getUnknownTypeHandlerName() ),
createBlock( getUnknownTypeHandlerName() ),
];

const result = isSingleUnknownBlock( blocks );

expect( result ).toBe( false );
} );

it( 'returns false if single block not of unknown type', () => {
const blocks = [
createBlock( getDefaultBlockName() ),
];

const result = isSingleUnknownBlock( blocks );

expect( result ).toBe( false );
} );

it( 'returns true if single block of unknown type', () => {
const blocks = [
createBlock( getUnknownTypeHandlerName() ),
];

const result = isSingleUnknownBlock( blocks );

expect( result ).toBe( true );
} );
} );

describe( 'isSingleUnmodifiedDefaultBlock', () => {
it( 'returns false if multiple blocks passed', () => {
const blocks = [
createBlock( getDefaultBlockName() ),
createBlock( getDefaultBlockName() ),
];

const result = isSingleUnmodifiedDefaultBlock( blocks );

expect( result ).toBe( false );
} );

it( 'returns false if single non-default block', () => {
const blocks = [
createBlock( getUnknownTypeHandlerName() ),
];

const result = isSingleUnmodifiedDefaultBlock( blocks );

expect( result ).toBe( false );
} );

it( 'returns false if single modified default block', () => {
const blocks = [
createBlock( getDefaultBlockName(), { modified: true } ),
];

const result = isSingleUnmodifiedDefaultBlock( blocks );

expect( result ).toBe( false );
} );

it( 'returns true if single unmodified default block', () => {
const blocks = [
createBlock( getDefaultBlockName() ),
];

const result = isSingleUnmodifiedDefaultBlock( blocks );

expect( result ).toBe( true );
} );
} );

describe( 'omitSingleUnmodifiedDefaultBlock', () => {
it( 'returns original array of blocks if not single unmodified default block', () => {
const blocks = [
createBlock( getUnknownTypeHandlerName() ),
];

const result = omitSingleUnmodifiedDefaultBlock( blocks );

expect( result ).toBe( blocks );
} );

it( 'returns an empty array if single unmodified default block', () => {
const blocks = [
createBlock( getDefaultBlockName() ),
];

const result = omitSingleUnmodifiedDefaultBlock( blocks );

expect( result ).toEqual( [] );
} );
} );

describe( 'removepSingleUnknownBlock', () => {
it( 'returns original content if not single block of unknown type', () => {
const blocks = [
createBlock( getDefaultBlockName() ),
];

const result = removepSingleUnknownBlock( '<p>foo</p>', blocks );

expect( result ).toBe( '<p>foo</p>' );
} );

it( 'returns removep-formatted content if single block of unknown type', () => {
const blocks = [
createBlock( getUnknownTypeHandlerName() ),
];

const result = removepSingleUnknownBlock( '<p>foo</p>', blocks );

expect( result ).toBe( 'foo' );
} );
} );
} );
71 changes: 60 additions & 11 deletions packages/editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ import createSelector from 'rememo';
/**
* WordPress dependencies
*/
import { serialize, getBlockType, getBlockTypes, hasBlockSupport, hasChildBlocksWithInserterSupport, getUnknownTypeHandlerName } from '@wordpress/blocks';
import {
serialize,
getBlockType,
getBlockTypes,
hasBlockSupport,
hasChildBlocksWithInserterSupport,
} from '@wordpress/blocks';
import { moment } from '@wordpress/date';
import { removep } from '@wordpress/autop';
import { applyFilters } from '@wordpress/hooks';

/***
* Module constants
Expand Down Expand Up @@ -349,15 +355,20 @@ export function isEditedPostSaveable( state ) {

/**
* Returns true if the edited post has content. A post has content if it has at
* least one block or otherwise has a non-empty content property assigned.
* least one saveable block or otherwise has a non-empty content property
* assigned.
*
* @param {Object} state Global application state.
*
* @return {boolean} Whether post has content.
*/
export function isEditedPostEmpty( state ) {
// While the condition of truthy content string would be sufficient for
// determining emptiness, testing saveable blocks length is a trivial
// operation by comparison. Since this function can be called frequently,
// optimize for the fast case where saveable blocks are non-empty.
return (
! getBlockCount( state ) &&
! getBlocksForSave( state ).length &&
! getEditedPostAttribute( state, 'content' )
);
}
Expand Down Expand Up @@ -1346,6 +1357,50 @@ export function getSuggestedPostFormat( state ) {
return null;
}

/**
* Returns a filtered set of blocks which are assumed to be used in
* consideration of the post's generated save content.
*
* @param {Object} state Editor state.
*
* @return {WPBlock[]} Filtered set of blocks for save.
*/
export function getBlocksForSave( state ) {
/**
* Filters the set of blocks assumed to be used in consideration of the
* post's generated save content.
*
* @param {Object[]} blocks Original editor state blocks.
*/
return applyFilters(
'editor.selectors.getBlocksForSave',
getBlocks( state ),
);
}

/**
* Returns a filtered HTML string for serialized given array of blocks.
*
* @param {Object} state Editor state.
* @param {WPBlock[]} blocks Array of blocks from which to generate content.
*
* @return {string} Filtered HTML string for serialized blocks.
*/
export function getBlockContent( state, blocks ) {
/**
* Filters the serialized content result of blocks.
*
* @param {string} content Original serialized value.
* @param {WPBlock[]} blocks Blocks from which serialized value is
* generated.
*/
return applyFilters(
'editor.selectors.getBlockContent',
serialize( blocks ),
blocks
);
}

/**
* Returns the content of the post being edited, preferring raw string edit
* before falling back to serialization of block state.
Expand All @@ -1361,13 +1416,7 @@ export const getEditedPostContent = createSelector(
return edits.content;
}

const blocks = getBlocks( state );

if ( blocks.length === 1 && blocks[ 0 ].name === getUnknownTypeHandlerName() ) {
return removep( serialize( blocks ) );
}

return serialize( blocks );
return getBlockContent( state, getBlocksForSave( state ) );
},
( state ) => [
state.editor.present.edits.content,
Expand Down
6 changes: 5 additions & 1 deletion test/e2e/specs/__snapshots__/splitting-merging.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

exports[`splitting and merging blocks should delete an empty first line 1`] = `
"<!-- wp:paragraph -->
<p></p>
<p>First</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Still Second</p>
<!-- /wp:paragraph -->"
`;

Expand Down
Loading