Skip to content

Commit

Permalink
Editor: Consider single unmodified default block as empty content
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth committed Sep 11, 2018
1 parent 5febf46 commit a634252
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 11 deletions.
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, hasChildBlocks, getUnknownTypeHandlerName } from '@wordpress/blocks';
import {
serialize,
getBlockType,
getBlockTypes,
hasBlockSupport,
hasChildBlocks,
} 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

0 comments on commit a634252

Please sign in to comment.