!! node.getAttribute( 'data-mce-bogus' ),
removeAttribute: ( attribute ) => attribute.indexOf( 'data-mce-' ) === 0,
filterString: ( string ) => string.replace( TINYMCE_ZWSP, '' ),
+ prepareEditableTree: this.props.prepareEditableTree,
} );
}
@@ -252,6 +254,7 @@ export class RichText extends Component {
element.setAttribute( 'data-mce-bogus', '1' );
return element;
},
+ prepareEditableTree: this.props.prepareEditableTree,
} );
}
@@ -413,10 +416,7 @@ export class RichText extends Component {
const record = this.createRecord();
const transformed = this.patterns.reduce( ( accumlator, transform ) => transform( accumlator ), record );
- // Don't apply changes if there's no transform. Content will be up to
- // date. In the future we could always let it flow back in the live DOM
- // if there are no performance issues.
- this.onChange( transformed, record === transformed );
+ this.onChange( transformed );
}
/**
@@ -780,6 +780,22 @@ export class RichText extends Component {
record.end = length;
this.applyRecord( record );
}
+
+ // If any format props update, reapply value.
+ const shouldReapply = Object.keys( this.props ).some( ( name ) => {
+ if ( name.indexOf( 'format_' ) !== 0 ) {
+ return false;
+ }
+
+ return Object.keys( this.props[ name ] ).some( ( subName ) => {
+ return this.props[ name ][ subName ] !== prevProps[ name ][ subName ];
+ } );
+ } );
+
+ if ( shouldReapply ) {
+ const record = this.formatToValue( value );
+ this.applyRecord( record );
+ }
}
formatToValue( value ) {
@@ -809,6 +825,20 @@ export class RichText extends Component {
return value;
}
+ valueToEditableHTML( value ) {
+ return unstableToDom( {
+ value,
+ multilineTag: this.multilineTag,
+ multilineWrapperTags: this.multilineWrapperTags,
+ createLinePadding( doc ) {
+ const element = doc.createElement( 'br' );
+ element.setAttribute( 'data-mce-bogus', '1' );
+ return element;
+ },
+ prepareEditableTree: this.props.prepareEditableTree,
+ } ).body.innerHTML;
+ }
+
valueToFormat( { formats, text } ) {
// Handle deprecated `children` and `node` sources.
if ( this.usedDeprecatedChildrenSource ) {
@@ -834,7 +864,6 @@ export class RichText extends Component {
const {
tagName: Tagname = 'div',
style,
- value,
wrapperClassName,
className,
inlineToolbar = false,
@@ -883,7 +912,7 @@ export class RichText extends Component {
getSettings={ this.getSettings }
onSetup={ this.onSetup }
style={ style }
- defaultValue={ value }
+ defaultValue={ this.valueToEditableHTML( record ) }
isPlaceholderVisible={ isPlaceholderVisible }
aria-label={ placeholder }
aria-autocomplete="list"
@@ -930,12 +959,15 @@ const RichTextContainer = compose( [
withBlockEditContext( ( context, ownProps ) => {
// When explicitly set as not selected, do nothing.
if ( ownProps.isSelected === false ) {
- return {};
+ return {
+ clientId: context.clientId,
+ };
}
// When explicitly set as selected, use the value stored in the context instead.
if ( ownProps.isSelected === true ) {
return {
isSelected: context.isSelected,
+ clientId: context.clientId,
};
}
@@ -943,6 +975,7 @@ const RichTextContainer = compose( [
return {
isSelected: context.isSelected && context.focusedElement === ownProps.instanceId,
setFocusedElement: context.setFocusedElement,
+ clientId: context.clientId,
};
} ),
withSelect( ( select ) => {
@@ -973,6 +1006,7 @@ const RichTextContainer = compose( [
};
} ),
withSafeTimeout,
+ withFilters( 'experimentalRichText' ),
] )( RichText );
RichTextContainer.Content = ( { value, tagName: Tag, multiline, ...props } ) => {
diff --git a/packages/editor/src/components/rich-text/tinymce.js b/packages/editor/src/components/rich-text/tinymce.js
index e3dfb4145a67c..11ebd13591e12 100644
--- a/packages/editor/src/components/rich-text/tinymce.js
+++ b/packages/editor/src/components/rich-text/tinymce.js
@@ -10,8 +10,6 @@ import classnames from 'classnames';
*/
import { Component, createElement } from '@wordpress/element';
import { BACKSPACE, DELETE, ENTER, LEFT, RIGHT } from '@wordpress/keycodes';
-import { toHTMLString } from '@wordpress/rich-text';
-import { children } from '@wordpress/blocks';
/**
* Internal dependencies
@@ -313,8 +311,6 @@ export default class TinyMCE extends Component {
isPlaceholderVisible,
onPaste,
onInput,
- multilineTag,
- multilineWrapperTags,
onKeyDown,
onKeyUp,
} = this.props;
@@ -332,28 +328,6 @@ export default class TinyMCE extends Component {
// If a default value is provided, render it into the DOM even before
// TinyMCE finishes initializing. This avoids a short delay by allowing
// us to show and focus the content before it's truly ready to edit.
- let initialHTML = defaultValue;
-
- // Guard for blocks passing `null` in onSplit callbacks. May be removed
- // if onSplit is revised to not pass a `null` value.
- if ( defaultValue === null ) {
- initialHTML = '';
- // Handle deprecated `children` and `node` sources.
- } else if ( Array.isArray( defaultValue ) ) {
- initialHTML = children.toHTML( defaultValue );
- } else if ( typeof defaultValue !== 'string' ) {
- initialHTML = toHTMLString( {
- value: defaultValue,
- multilineTag,
- multilineWrapperTags,
- } );
- }
-
- if ( initialHTML === '' ) {
- // Ensure the field is ready to receive focus by TinyMCE.
- initialHTML = '
';
- }
-
return createElement( tagName, {
...ariaProps,
className: classnames( className, 'editor-rich-text__tinymce' ),
@@ -362,7 +336,7 @@ export default class TinyMCE extends Component {
ref: this.bindEditorNode,
style,
suppressContentEditableWarning: true,
- dangerouslySetInnerHTML: { __html: initialHTML },
+ dangerouslySetInnerHTML: { __html: defaultValue },
onPaste,
onInput,
onFocus: this.onFocus,
diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js
index ec5a7eb175938..5ab6a34e6c903 100644
--- a/packages/rich-text/src/create.js
+++ b/packages/rich-text/src/create.js
@@ -57,6 +57,10 @@ function toFormat( { type, attributes } ) {
return attributes ? { type, attributes } : { type };
}
+ if ( formatType.__experimentalCreatePrepareEditableTree ) {
+ return null;
+ }
+
if ( ! attributes ) {
return { type: formatType.name };
}
@@ -359,11 +363,13 @@ function createFromElement( {
} ),
} );
- // Reuse the last format if it's equal.
- if ( isFormatEqual( newFormat, lastFormat ) ) {
- format = lastFormat;
- } else {
- format = newFormat;
+ if ( newFormat ) {
+ // Reuse the last format if it's equal.
+ if ( isFormatEqual( newFormat, lastFormat ) ) {
+ format = lastFormat;
+ } else {
+ format = newFormat;
+ }
}
}
diff --git a/packages/rich-text/src/register-format-type.js b/packages/rich-text/src/register-format-type.js
index 1b475bf5d89d9..38af31b958e90 100644
--- a/packages/rich-text/src/register-format-type.js
+++ b/packages/rich-text/src/register-format-type.js
@@ -1,12 +1,8 @@
-/**
- * External dependencies
- */
-import { isFunction } from 'lodash';
-
/**
* WordPress dependencies
*/
-import { select, dispatch } from '@wordpress/data';
+import { select, dispatch, withSelect } from '@wordpress/data';
+import { addFilter } from '@wordpress/hooks';
/**
* Registers a new format provided a unique name and an object defining its
@@ -45,13 +41,6 @@ export function registerFormatType( name, settings ) {
return;
}
- if ( ! settings || ! isFunction( settings.edit ) ) {
- window.console.error(
- 'The "edit" property must be specified and must be a valid function.'
- );
- return;
- }
-
if (
typeof settings.tagName !== 'string' ||
settings.tagName === ''
@@ -124,5 +113,33 @@ export function registerFormatType( name, settings ) {
dispatch( 'core/rich-text' ).addFormatTypes( settings );
+ if (
+ settings.__experimentalCreatePrepareEditableTree &&
+ settings.__experimentalGetPropsForEditableTreePreparation
+ ) {
+ addFilter( 'experimentalRichText', name, ( OriginalComponent ) => {
+ return withSelect( ( sel, { clientId, identifier } ) => ( {
+ [ `format_${ name }` ]: settings.__experimentalGetPropsForEditableTreePreparation(
+ sel,
+ {
+ richTextIdentifier: identifier,
+ blockClientId: clientId,
+ }
+ ),
+ } ) )( ( props ) => (
+
+ ) );
+ } );
+ }
+
return settings;
}
diff --git a/packages/rich-text/src/test/register-format-type.js b/packages/rich-text/src/test/register-format-type.js
index cde3c900e8b41..5fc78630be2ba 100644
--- a/packages/rich-text/src/test/register-format-type.js
+++ b/packages/rich-text/src/test/register-format-type.js
@@ -94,24 +94,6 @@ describe( 'registerFormatType', () => {
expect( duplicateFormat ).toBeUndefined();
} );
- it( 'should error on undefined edit property', () => {
- const format = registerFormatType( 'plugin/test', {
- ...validSettings,
- edit: undefined,
- } );
- expect( console ).toHaveErroredWith( 'The "edit" property must be specified and must be a valid function.' );
- expect( format ).toBeUndefined();
- } );
-
- it( 'should reject formats with an invalid edit function', () => {
- const format = registerFormatType( validName, {
- ...validSettings,
- edit: 'not-a-function',
- } );
- expect( console ).toHaveErroredWith( 'The "edit" property must be specified and must be a valid function.' );
- expect( format ).toBeUndefined();
- } );
-
it( 'should reject formats without tag name', () => {
const settings = { ...validSettings };
delete settings.tagName;
diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js
index b5851817b5db7..b582c77083d9a 100644
--- a/packages/rich-text/src/to-dom.js
+++ b/packages/rich-text/src/to-dom.js
@@ -132,17 +132,27 @@ function padEmptyLines( { element, createLinePadding, multilineWrapperTags } ) {
}
}
+function prepareFormats( prepareEditableTree = [], value ) {
+ return prepareEditableTree.reduce( ( accumlator, fn ) => {
+ return fn( accumlator, value.text );
+ }, value.formats );
+}
+
export function toDom( {
value,
multilineTag,
multilineWrapperTags,
createLinePadding,
+ prepareEditableTree,
} ) {
let startPath = [];
let endPath = [];
const tree = toTree( {
- value,
+ value: {
+ ...value,
+ formats: prepareFormats( prepareEditableTree, value ),
+ },
multilineTag,
multilineWrapperTags,
createEmpty,
@@ -188,6 +198,7 @@ export function apply( {
multilineTag,
multilineWrapperTags,
createLinePadding,
+ prepareEditableTree,
} ) {
// Construct a new element tree in memory.
const { body, selection } = toDom( {
@@ -195,6 +206,7 @@ export function apply( {
multilineTag,
multilineWrapperTags,
createLinePadding,
+ prepareEditableTree,
} );
applyValue( body, current );
diff --git a/packages/rich-text/src/unregister-format-type.js b/packages/rich-text/src/unregister-format-type.js
index a01b648622342..cffaa3d025b94 100644
--- a/packages/rich-text/src/unregister-format-type.js
+++ b/packages/rich-text/src/unregister-format-type.js
@@ -2,6 +2,7 @@
* WordPress dependencies
*/
import { select, dispatch } from '@wordpress/data';
+import { removeFilter } from '@wordpress/hooks';
/**
* Unregisters a format.
@@ -21,6 +22,13 @@ export function unregisterFormatType( name ) {
return;
}
+ if (
+ oldFormat.__experimentalCreatePrepareEditableTree &&
+ oldFormat.__experimentalGetPropsForEditableTreePreparation
+ ) {
+ removeFilter( 'experimentalRichText', name );
+ }
+
dispatch( 'core/rich-text' ).removeFormatTypes( name );
return oldFormat;
diff --git a/test/e2e/specs/blocks/__snapshots__/quote.test.js.snap b/test/e2e/specs/blocks/__snapshots__/quote.test.js.snap
index 5257716d67d00..6a46b767390ef 100644
--- a/test/e2e/specs/blocks/__snapshots__/quote.test.js.snap
+++ b/test/e2e/specs/blocks/__snapshots__/quote.test.js.snap
@@ -58,11 +58,7 @@ exports[`Quote can be converted to paragraphs and renders a paragraph for the ci
"
`;
-exports[`Quote can be converted to paragraphs and renders a void paragraph if both the cite and quote are void 1`] = `
-"
-
-"
-`;
+exports[`Quote can be converted to paragraphs and renders a void paragraph if both the cite and quote are void 1`] = `""`;
exports[`Quote can be converted to paragraphs and renders one paragraph block per
within quote 1`] = `
"
From a3785cd15bcfaa17df20f7a6afc74d9254fe2c47 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?=
Date: Fri, 9 Nov 2018 09:10:58 +0100
Subject: [PATCH 054/106] Block API: Normalize block type function argument
(#11490)
* Block API: Normalize usage of getBlockAttributes by allowing block name as param
* Block API: Normalize usage of getSaveContent by allowing block name as param
* Block API: Normalize usage of getSaveElement by allowing block name as param
* Tests: Add unit tests covering normalization of block type
* Docs: Add a note about more flexible block API methods
* Refactor getBlockTransforms to use block type normalization
* Blocks: Fix JSDoc for getBlockAttributes function
---
packages/block-library/src/heading/index.js | 3 +--
packages/block-library/src/image/index.js | 4 +---
packages/block-library/src/list/index.js | 3 +--
packages/blocks/CHANGELOG.md | 6 +++++
packages/blocks/src/api/factory.js | 10 ++++----
packages/blocks/src/api/parser.js | 10 ++++----
packages/blocks/src/api/raw-handling/index.js | 3 +--
packages/blocks/src/api/serializer.js | 23 +++++++++++--------
packages/blocks/src/api/test/factory.js | 21 ++++++++++++++++-
packages/blocks/src/api/test/parser.js | 20 ++++++++++++++++
packages/blocks/src/api/test/serializer.js | 19 ++++++++++++++-
packages/blocks/src/api/test/validation.js | 16 +++++++++++--
packages/blocks/src/api/utils.js | 19 ++++++++++++++-
packages/blocks/src/api/validation.js | 10 ++++----
.../src/components/block-compare/index.js | 11 ++++-----
.../src/components/inner-blocks/test/index.js | 3 +--
16 files changed, 136 insertions(+), 45 deletions(-)
diff --git a/packages/block-library/src/heading/index.js b/packages/block-library/src/heading/index.js
index 6f98add18effe..148d062934074 100644
--- a/packages/block-library/src/heading/index.js
+++ b/packages/block-library/src/heading/index.js
@@ -11,7 +11,6 @@ import {
createBlock,
getPhrasingContentSchema,
getBlockAttributes,
- getBlockType,
} from '@wordpress/blocks';
import { RichText } from '@wordpress/editor';
import {
@@ -101,7 +100,7 @@ export const settings = {
transform( node ) {
return createBlock( 'core/heading', {
...getBlockAttributes(
- getBlockType( 'core/heading' ),
+ 'core/heading',
node.outerHTML
),
level: getLevelFromHeadingNodeName( node.nodeName ),
diff --git a/packages/block-library/src/image/index.js b/packages/block-library/src/image/index.js
index 83a022388d35d..4f6619a6c6e52 100644
--- a/packages/block-library/src/image/index.js
+++ b/packages/block-library/src/image/index.js
@@ -11,7 +11,6 @@ import { __ } from '@wordpress/i18n';
import {
createBlock,
getBlockAttributes,
- getBlockType,
getPhrasingContentSchema,
} from '@wordpress/blocks';
import { RichText } from '@wordpress/editor';
@@ -133,8 +132,7 @@ export const settings = {
const anchorElement = node.querySelector( 'a' );
const linkDestination = anchorElement && anchorElement.href ? 'custom' : undefined;
const href = anchorElement && anchorElement.href ? anchorElement.href : undefined;
- const blockType = getBlockType( 'core/image' );
- const attributes = getBlockAttributes( blockType, node.outerHTML, { align, id, linkDestination, href } );
+ const attributes = getBlockAttributes( 'core/image', node.outerHTML, { align, id, linkDestination, href } );
return createBlock( 'core/image', attributes );
},
},
diff --git a/packages/block-library/src/list/index.js b/packages/block-library/src/list/index.js
index f2c9485e2bccb..9fe52078e8993 100644
--- a/packages/block-library/src/list/index.js
+++ b/packages/block-library/src/list/index.js
@@ -12,7 +12,6 @@ import {
createBlock,
getPhrasingContentSchema,
getBlockAttributes,
- getBlockType,
} from '@wordpress/blocks';
import {
BlockControls,
@@ -108,7 +107,7 @@ export const settings = {
transform( node ) {
return createBlock( 'core/list', {
...getBlockAttributes(
- getBlockType( 'core/list' ),
+ 'core/list',
node.outerHTML
),
ordered: node.nodeName === 'OL',
diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md
index f77cfe3b809a6..b3eaf4372a0de 100644
--- a/packages/blocks/CHANGELOG.md
+++ b/packages/blocks/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 5.3.0 (Unreleased)
+
+### New feature
+
+- `getBlockAttributes`, `getBlockTransforms`, `getSaveContent`, `getSaveElement` and `isValidBlockContent` methods can now take also block's name as the first param ([#11490](https://github.com/WordPress/gutenberg/pull/11490)). Passing a block's type object continues to work as before.
+
## 5.2.0 (2018-11-09)
- Paste: Google Docs: fix nested formatting, sub, sup and del.
diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js
index d59707e59bd89..f51dfb7e6576d 100644
--- a/packages/blocks/src/api/factory.js
+++ b/packages/blocks/src/api/factory.js
@@ -25,6 +25,7 @@ import { createHooks, applyFilters } from '@wordpress/hooks';
* Internal dependencies
*/
import { getBlockType, getBlockTypes } from './registration';
+import { normalizeBlockType } from './utils';
/**
* Returns a block object given its type and attributes.
@@ -279,13 +280,13 @@ export function findTransform( transforms, predicate ) {
* transform object includes `blockName` as a property.
*
* @param {string} direction Transform direction ("to", "from").
- * @param {?string} blockName Optional block name.
+ * @param {string|Object} blockTypeOrName Block type or name.
*
* @return {Array} Block transforms for direction.
*/
-export function getBlockTransforms( direction, blockName ) {
+export function getBlockTransforms( direction, blockTypeOrName ) {
// When retrieving transforms for all block types, recurse into self.
- if ( blockName === undefined ) {
+ if ( blockTypeOrName === undefined ) {
return flatMap(
getBlockTypes(),
( { name } ) => getBlockTransforms( direction, name )
@@ -293,7 +294,8 @@ export function getBlockTransforms( direction, blockName ) {
}
// Validate that block type exists and has array of direction.
- const { transforms } = getBlockType( blockName ) || {};
+ const blockType = normalizeBlockType( blockTypeOrName );
+ const { name: blockName, transforms } = blockType || {};
if ( ! transforms || ! Array.isArray( transforms[ direction ] ) ) {
return [];
}
diff --git a/packages/blocks/src/api/parser.js b/packages/blocks/src/api/parser.js
index 3e9ce946409ab..597ceffff726c 100644
--- a/packages/blocks/src/api/parser.js
+++ b/packages/blocks/src/api/parser.js
@@ -23,6 +23,7 @@ import { createBlock } from './factory';
import { isValidBlockContent } from './validation';
import { getCommentDelimitedContent } from './serializer';
import { attr, html, text, query, node, children, prop } from './matchers';
+import { normalizeBlockType } from './utils';
/**
* Sources which are guaranteed to return a string value.
@@ -277,13 +278,14 @@ export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, com
/**
* Returns the block attributes of a registered block node given its type.
*
- * @param {?Object} blockType Block type.
- * @param {string} innerHTML Raw block content.
- * @param {?Object} attributes Known block attributes (from delimiters).
+ * @param {string|Object} blockTypeOrName Block type or name.
+ * @param {string} innerHTML Raw block content.
+ * @param {?Object} attributes Known block attributes (from delimiters).
*
* @return {Object} All block attributes.
*/
-export function getBlockAttributes( blockType, innerHTML, attributes = {} ) {
+export function getBlockAttributes( blockTypeOrName, innerHTML, attributes = {} ) {
+ const blockType = normalizeBlockType( blockTypeOrName );
const blockAttributes = mapValues( blockType.attributes, ( attributeSchema, attributeKey ) => {
return getBlockAttribute( attributeKey, attributeSchema, innerHTML, attributes );
} );
diff --git a/packages/blocks/src/api/raw-handling/index.js b/packages/blocks/src/api/raw-handling/index.js
index c38cdab46e0dc..db51f13e0fc6c 100644
--- a/packages/blocks/src/api/raw-handling/index.js
+++ b/packages/blocks/src/api/raw-handling/index.js
@@ -7,7 +7,6 @@ import { flatMap, filter, compact } from 'lodash';
* Internal dependencies
*/
import { createBlock, getBlockTransforms, findTransform } from '../factory';
-import { getBlockType } from '../registration';
import { getBlockContent } from '../serializer';
import { getBlockAttributes, parseWithGrammar } from '../parser';
import normaliseBlocks from './normalise-blocks';
@@ -103,7 +102,7 @@ function htmlToBlocks( { html, rawTransforms } ) {
return createBlock(
blockName,
getBlockAttributes(
- getBlockType( blockName ),
+ blockName,
node.outerHTML
)
);
diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js
index c3fc87e8f7023..443b68a14a593 100644
--- a/packages/blocks/src/api/serializer.js
+++ b/packages/blocks/src/api/serializer.js
@@ -18,6 +18,7 @@ import {
getFreeformContentHandlerName,
getUnregisteredTypeHandlerName,
} from './registration';
+import { normalizeBlockType } from './utils';
import BlockContentProvider from '../block-content-provider';
/**
@@ -54,13 +55,14 @@ export function getBlockMenuDefaultClassName( blockName ) {
* Given a block type containing a save render implementation and attributes, returns the
* enhanced element to be saved or string when raw HTML expected.
*
- * @param {Object} blockType Block type.
- * @param {Object} attributes Block attributes.
- * @param {?Array} innerBlocks Nested blocks.
+ * @param {string|Object} blockTypeOrName Block type or name.
+ * @param {Object} attributes Block attributes.
+ * @param {?Array} innerBlocks Nested blocks.
*
* @return {Object|string} Save element or raw HTML string.
*/
-export function getSaveElement( blockType, attributes, innerBlocks = [] ) {
+export function getSaveElement( blockTypeOrName, attributes, innerBlocks = [] ) {
+ const blockType = normalizeBlockType( blockTypeOrName );
let { save } = blockType;
// Component classes are unsupported for save since serialization must
@@ -113,13 +115,15 @@ export function getSaveElement( blockType, attributes, innerBlocks = [] ) {
* Given a block type containing a save render implementation and attributes, returns the
* static markup to be saved.
*
- * @param {Object} blockType Block type.
- * @param {Object} attributes Block attributes.
- * @param {?Array} innerBlocks Nested blocks.
+ * @param {string|Object} blockTypeOrName Block type or name.
+ * @param {Object} attributes Block attributes.
+ * @param {?Array} innerBlocks Nested blocks.
*
* @return {string} Save content.
*/
-export function getSaveContent( blockType, attributes, innerBlocks ) {
+export function getSaveContent( blockTypeOrName, attributes, innerBlocks ) {
+ const blockType = normalizeBlockType( blockTypeOrName );
+
return renderToString( getSaveElement( blockType, attributes, innerBlocks ) );
}
@@ -199,7 +203,6 @@ export function serializeAttributes( attributes ) {
*/
export function getBlockContent( block ) {
// @todo why not getBlockInnerHtml?
- const blockType = getBlockType( block.name );
// If block was parsed as invalid or encounters an error while generating
// save content, use original content instead to avoid content loss. If a
@@ -209,7 +212,7 @@ export function getBlockContent( block ) {
let saveContent = block.originalContent;
if ( block.isValid || block.innerBlocks.length ) {
try {
- saveContent = getSaveContent( blockType, block.attributes, block.innerBlocks );
+ saveContent = getSaveContent( block.name, block.attributes, block.innerBlocks );
} catch ( error ) {}
}
diff --git a/packages/blocks/src/api/test/factory.js b/packages/blocks/src/api/test/factory.js
index d8c46a857f90d..eaf222b8b5c86 100644
--- a/packages/blocks/src/api/test/factory.js
+++ b/packages/blocks/src/api/test/factory.js
@@ -15,7 +15,12 @@ import {
getBlockTransforms,
findTransform,
} from '../factory';
-import { getBlockTypes, unregisterBlockType, registerBlockType } from '../registration';
+import {
+ getBlockType,
+ getBlockTypes,
+ registerBlockType,
+ unregisterBlockType,
+} from '../registration';
describe( 'block factory', () => {
const defaultBlockSettings = {
@@ -1181,6 +1186,20 @@ describe( 'block factory', () => {
},
] );
} );
+
+ it( 'should return single block type transforms when passed as an object', () => {
+ const transforms = getBlockTransforms(
+ 'from',
+ getBlockType( 'core/transform-from-text-block-1' )
+ );
+
+ expect( transforms ).toEqual( [
+ {
+ blocks: [ 'core/text-block' ],
+ blockName: 'core/transform-from-text-block-1',
+ },
+ ] );
+ } );
} );
describe( 'findTransform', () => {
diff --git a/packages/blocks/src/api/test/parser.js b/packages/blocks/src/api/test/parser.js
index e55a2546a9bca..bc059542b0142 100644
--- a/packages/blocks/src/api/test/parser.js
+++ b/packages/blocks/src/api/test/parser.js
@@ -343,6 +343,26 @@ describe( 'block parser', () => {
undefAmbiguousStringWithDefault: 'ok',
} );
} );
+
+ it( 'should work when block type is passed as string', () => {
+ registerBlockType( 'core/meal', {
+ title: 'Meal',
+ category: 'widgets',
+ attributes: {
+ content: {
+ source: 'text',
+ selector: 'div',
+ },
+ },
+ save: () => {},
+ } );
+
+ const innerHTML = 'Ribs
';
+
+ expect( getBlockAttributes( 'core/meal', innerHTML ) ).toEqual( {
+ content: 'Ribs',
+ } );
+ } );
} );
describe( 'getMigratedBlock', () => {
diff --git a/packages/blocks/src/api/test/serializer.js b/packages/blocks/src/api/test/serializer.js
index d0be732c60efb..a0a5eb2461d3b 100644
--- a/packages/blocks/src/api/test/serializer.js
+++ b/packages/blocks/src/api/test/serializer.js
@@ -39,17 +39,34 @@ describe( 'block serializer', () => {
describe( 'getSaveContent()', () => {
describe( 'function save', () => {
+ const fruitBlockSave = ( { attributes } ) => createElement( 'div', null, attributes.fruit );
+
it( 'should return element as string if save returns element', () => {
const saved = getSaveContent(
{
- save: ( { attributes } ) => createElement( 'div', null, attributes.fruit ),
name: 'core/fruit',
+ save: fruitBlockSave,
},
{ fruit: 'Bananas' }
);
expect( saved ).toBe( 'Bananas
' );
} );
+
+ it( 'should work when block type is passed as string', () => {
+ registerBlockType( 'core/fruit', {
+ title: 'Fruit',
+ category: 'widgets',
+ save: fruitBlockSave,
+ } );
+
+ const saved = getSaveContent(
+ 'core/fruit',
+ { fruit: 'Bananas' }
+ );
+
+ expect( saved ).toBe( 'Bananas
' );
+ } );
} );
describe( 'component save', () => {
diff --git a/packages/blocks/src/api/test/validation.js b/packages/blocks/src/api/test/validation.js
index fcc02e490447d..0fd199f43b477 100644
--- a/packages/blocks/src/api/test/validation.js
+++ b/packages/blocks/src/api/test/validation.js
@@ -558,7 +558,7 @@ describe( 'validation', () => {
registerBlockType( 'core/test-block', defaultBlockSettings );
const isValid = isValidBlockContent(
- getBlockType( 'core/test-block' ),
+ 'core/test-block',
{ fruit: 'Bananas' },
'Apples'
);
@@ -577,7 +577,7 @@ describe( 'validation', () => {
} );
const isValid = isValidBlockContent(
- getBlockType( 'core/test-block' ),
+ 'core/test-block',
{ fruit: 'Bananas' },
'Bananas'
);
@@ -589,6 +589,18 @@ describe( 'validation', () => {
it( 'returns true is block is valid', () => {
registerBlockType( 'core/test-block', defaultBlockSettings );
+ const isValid = isValidBlockContent(
+ 'core/test-block',
+ { fruit: 'Bananas' },
+ 'Bananas'
+ );
+
+ expect( isValid ).toBe( true );
+ } );
+
+ it( 'works also when block type object is passed as object', () => {
+ registerBlockType( 'core/test-block', defaultBlockSettings );
+
const isValid = isValidBlockContent(
getBlockType( 'core/test-block' ),
{ fruit: 'Bananas' },
diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js
index 810aa24278456..d5552a492aaa1 100644
--- a/packages/blocks/src/api/utils.js
+++ b/packages/blocks/src/api/utils.js
@@ -13,7 +13,7 @@ import { Component, isValidElement } from '@wordpress/element';
/**
* Internal dependencies
*/
-import { getDefaultBlockName } from './registration';
+import { getBlockType, getDefaultBlockName } from './registration';
import { createBlock } from './factory';
/**
@@ -104,3 +104,20 @@ export function normalizeIconObject( icon ) {
return icon;
}
+
+/**
+ * Normalizes block type passed as param. When string is passed then
+ * it converts it to the matching block type object.
+ * It passes the original object otherwise.
+ *
+ * @param {string|Object} blockTypeOrName Block type or name.
+ *
+ * @return {?Object} Block type.
+ */
+export function normalizeBlockType( blockTypeOrName ) {
+ if ( isString( blockTypeOrName ) ) {
+ return getBlockType( blockTypeOrName );
+ }
+
+ return blockTypeOrName;
+}
diff --git a/packages/blocks/src/api/validation.js b/packages/blocks/src/api/validation.js
index a1ac55c1dbe48..5685756d7f4dc 100644
--- a/packages/blocks/src/api/validation.js
+++ b/packages/blocks/src/api/validation.js
@@ -13,6 +13,7 @@ import deprecated from '@wordpress/deprecated';
* Internal dependencies
*/
import { getSaveContent } from './serializer';
+import { normalizeBlockType } from './utils';
/**
* Globally matches any consecutive whitespace
@@ -508,13 +509,14 @@ export function isValidBlock( innerHTML, blockType, attributes ) {
*
* Logs to console in development environments when invalid.
*
- * @param {string} blockType Block type.
- * @param {Object} attributes Parsed block attributes.
- * @param {string} innerHTML Original block content.
+ * @param {string|Object} blockTypeOrName Block type.
+ * @param {Object} attributes Parsed block attributes.
+ * @param {string} innerHTML Original block content.
*
* @return {boolean} Whether block is valid.
*/
-export function isValidBlockContent( blockType, attributes, innerHTML ) {
+export function isValidBlockContent( blockTypeOrName, attributes, innerHTML ) {
+ const blockType = normalizeBlockType( blockTypeOrName );
let saveContent;
try {
saveContent = getSaveContent( blockType, attributes );
diff --git a/packages/editor/src/components/block-compare/index.js b/packages/editor/src/components/block-compare/index.js
index 87ec2208a8108..0782976519201 100644
--- a/packages/editor/src/components/block-compare/index.js
+++ b/packages/editor/src/components/block-compare/index.js
@@ -10,7 +10,7 @@ import { diffChars } from 'diff';
*/
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
-import { getBlockType, getSaveContent, getSaveElement } from '@wordpress/blocks';
+import { getSaveContent, getSaveElement } from '@wordpress/blocks';
/**
* Internal dependencies
@@ -32,12 +32,9 @@ class BlockCompare extends Component {
}
getOriginalContent( block ) {
- // Get current block details
- const blockType = getBlockType( block.name );
-
return {
rawContent: block.originalContent,
- renderedContent: getSaveElement( blockType, block.attributes ),
+ renderedContent: getSaveElement( block.name, block.attributes ),
};
}
@@ -46,8 +43,8 @@ class BlockCompare extends Component {
const newBlocks = castArray( block );
// Get converted block details
- const newContent = newBlocks.map( ( item ) => getSaveContent( getBlockType( item.name ), item.attributes, item.innerBlocks ) );
- const renderedContent = newBlocks.map( ( item ) => getSaveElement( getBlockType( item.name ), item.attributes, item.innerBlocks ) );
+ const newContent = newBlocks.map( ( item ) => getSaveContent( item.name, item.attributes, item.innerBlocks ) );
+ const renderedContent = newBlocks.map( ( item ) => getSaveElement( item.name, item.attributes, item.innerBlocks ) );
return {
rawContent: newContent.join( '' ),
diff --git a/packages/editor/src/components/inner-blocks/test/index.js b/packages/editor/src/components/inner-blocks/test/index.js
index 6bca8a8d761ea..19bfd1bc383d7 100644
--- a/packages/editor/src/components/inner-blocks/test/index.js
+++ b/packages/editor/src/components/inner-blocks/test/index.js
@@ -3,7 +3,6 @@
*/
import {
createBlock,
- getBlockType,
getBlockTypes,
getSaveElement,
registerBlockType,
@@ -52,7 +51,7 @@ describe( 'InnerBlocks', () => {
const saved = renderToString(
getSaveElement(
- getBlockType( 'core/fruit' ),
+ 'core/fruit',
{ fruit: 'Bananas' },
[ createBlock( 'core/fruit', { fruit: 'Apples' } ) ],
)
From b9c909f87b93c56309a6c6b02babc45c93026c0d Mon Sep 17 00:00:00 2001
From: Jorge
Date: Fri, 9 Nov 2018 08:14:25 +0000
Subject: [PATCH 055/106] Always show block icon on the block switcher when a
single block is selected (#11600)
---
.../src/components/block-switcher/index.js | 15 +++++++++++++-
.../src/components/block-switcher/style.scss | 20 +++++++++++++++----
2 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/packages/editor/src/components/block-switcher/index.js b/packages/editor/src/components/block-switcher/index.js
index d298834293471..292dacf98567e 100644
--- a/packages/editor/src/components/block-switcher/index.js
+++ b/packages/editor/src/components/block-switcher/index.js
@@ -58,7 +58,20 @@ export class BlockSwitcher extends Component {
const hasStyles = blocks.length === 1 && get( blockType, [ 'styles' ], [] ).length !== 0;
if ( ! hasStyles && ! possibleBlockTransformations.length ) {
- return null;
+ if ( blocks.length > 1 ) {
+ return null;
+ }
+ return (
+
+
+
+
+
+ );
}
return (
diff --git a/packages/editor/src/components/block-switcher/style.scss b/packages/editor/src/components/block-switcher/style.scss
index 503b312d3b647..daebe5a47ea72 100644
--- a/packages/editor/src/components/block-switcher/style.scss
+++ b/packages/editor/src/components/block-switcher/style.scss
@@ -3,15 +3,27 @@
height: $icon-button-size;
}
-// Style this the same as the block buttons in the library.
-// Needs specificiity to override the icon button.
-.components-icon-button.editor-block-switcher__toggle {
- width: auto;
+.components-icon-button.editor-block-switcher__toggle,
+.components-icon-button.editor-block-switcher__no-switcher-icon {
margin: 0;
display: block;
height: $icon-button-size;
padding: 3px;
+}
+
+.components-icon-button.editor-block-switcher__no-switcher-icon {
+ width: $icon-button-size + 6px + 6px;
+ .editor-block-icon {
+ margin-right: auto;
+ margin-left: auto;
+ }
+}
+
+// Style this the same as the block buttons in the library.
+// Needs specificiity to override the icon button.
+.components-icon-button.editor-block-switcher__toggle {
+ width: auto;
// Unset icon button styles.
&:active,
&:not(:disabled):not([aria-disabled="true"]):hover,
From b814d88612b7c1d4452508aeb9906c98605ca723 Mon Sep 17 00:00:00 2001
From: Andrew Ozz
Date: Fri, 9 Nov 2018 10:17:15 +0200
Subject: [PATCH 056/106] Make the kitchensink button removable from plugins
(#10964)
---
lib/client-assets.php | 40 ++++++++++------------
packages/block-library/src/classic/edit.js | 9 +++++
2 files changed, 28 insertions(+), 21 deletions(-)
diff --git a/lib/client-assets.php b/lib/client-assets.php
index 3450b21856b07..9bb0180c92963 100644
--- a/lib/client-assets.php
+++ b/lib/client-assets.php
@@ -598,28 +598,26 @@ function gutenberg_register_scripts_and_styles() {
),
'toolbar1' => implode(
',',
- array_merge(
- apply_filters(
- 'mce_buttons',
- array(
- 'formatselect',
- 'bold',
- 'italic',
- 'bullist',
- 'numlist',
- 'blockquote',
- 'alignleft',
- 'aligncenter',
- 'alignright',
- 'link',
- 'unlink',
- 'wp_more',
- 'spellchecker',
- 'wp_add_media',
- ),
- 'editor'
+ apply_filters(
+ 'mce_buttons',
+ array(
+ 'formatselect',
+ 'bold',
+ 'italic',
+ 'bullist',
+ 'numlist',
+ 'blockquote',
+ 'alignleft',
+ 'aligncenter',
+ 'alignright',
+ 'link',
+ 'unlink',
+ 'wp_more',
+ 'spellchecker',
+ 'wp_add_media',
+ 'kitchensink',
),
- array( 'kitchensink' )
+ 'editor'
)
),
'toolbar2' => implode(
diff --git a/packages/block-library/src/classic/edit.js b/packages/block-library/src/classic/edit.js
index 6bede6a2df318..97307e9119553 100644
--- a/packages/block-library/src/classic/edit.js
+++ b/packages/block-library/src/classic/edit.js
@@ -115,6 +115,7 @@ export default class ClassicEdit extends Component {
}
} );
+ // TODO: the following is for back-compat with WP 4.9, not needed in WP 5.0. Remove it after the release.
editor.addButton( 'kitchensink', {
tooltip: _x( 'More', 'button to expand options' ),
icon: 'dashicon dashicons-editor-kitchensink',
@@ -127,11 +128,19 @@ export default class ClassicEdit extends Component {
},
} );
+ // Show the second, third, etc. toolbars when the `kitchensink` button is removed by a plugin.
+ editor.on( 'init', function() {
+ if ( editor.settings.toolbar1 && editor.settings.toolbar1.indexOf( 'kitchensink' ) === -1 ) {
+ editor.dom.addClass( ref, 'has-advanced-toolbar' );
+ }
+ } );
+
editor.addButton( 'wp_add_media', {
tooltip: __( 'Insert Media' ),
icon: 'dashicon dashicons-admin-media',
cmd: 'WP_Medialib',
} );
+ // End TODO.
editor.on( 'init', () => {
const rootNode = this.editor.getBody();
From 8e396e793a37c921cd5cb68716a1d4962690cb5c Mon Sep 17 00:00:00 2001
From: Joen Asmussen
Date: Fri, 9 Nov 2018 09:41:36 +0100
Subject: [PATCH 057/106] Try: Improve columns block (#11620)
* Try: Improve columns block
This PR aims to improve the column block usability, as well as fix a few issues:
1. It makes the individual columns unclickable by using pointer events. Since these are not actionable yet, being able to select them is not really helpful.
2. It improves the UI around reusable blocks, by fixing an issue where even though contents should be disabled when in a reusable block, it was still selectable if there were nested blocks.
3. It further improves the reusable block UI by fixing a regression where the reusable block editing bar did not push down content sufficiently.
4. It refactors how margins in the columns block work, to be simpler and more predictable, and makes it behave similar to every other block in that regard.
* Improve the margins of an empty colums block.
---
assets/stylesheets/_z-index.scss | 3 ++
.../src/block/edit-panel/style.scss | 4 ++-
.../block-library/src/columns/editor.scss | 28 +++++++++++--------
packages/components/src/disabled/style.scss | 5 ++++
.../src/components/block-list/style.scss | 7 +----
5 files changed, 29 insertions(+), 18 deletions(-)
diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss
index b494e9f6fdc50..39e1bb461d46b 100644
--- a/assets/stylesheets/_z-index.scss
+++ b/assets/stylesheets/_z-index.scss
@@ -37,6 +37,9 @@ $z-layers: (
// Active pill button
".components-button.is-button {:focus or .is-primary}": 1,
+ // Reusable blocks UI, needs to be above sibling inserter.
+ ".editor-block-list__layout .reusable-block-edit-panel": 7,
+
// The draggable element should show up above the entire UI
".components-draggable__clone": 1000000000,
diff --git a/packages/block-library/src/block/edit-panel/style.scss b/packages/block-library/src/block/edit-panel/style.scss
index ff73c68fee7e3..854cfe03f282f 100644
--- a/packages/block-library/src/block/edit-panel/style.scss
+++ b/packages/block-library/src/block/edit-panel/style.scss
@@ -8,8 +8,10 @@
font-size: $default-font-size;
position: relative;
top: -$block-padding;
- margin: 0 (-$block-padding) (-$block-padding) (-$block-padding);
+ margin: 0 (-$block-padding);
padding: $grid-size $block-padding;
+ position: relative;
+ z-index: z-index(".editor-block-list__layout .reusable-block-edit-panel");
// Show a smaller padding when nested.
.editor-block-list__layout & {
diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss
index ebe614483c4a5..a96a6dcf8c1ea 100644
--- a/packages/block-library/src/columns/editor.scss
+++ b/packages/block-library/src/columns/editor.scss
@@ -25,15 +25,6 @@
}
}
-// This block has flex container children. Flex container margins do not collapse: https://www.w3.org/TR/css-flexbox-1/#flex-containers.
-// Therefore, let's at least not add any additional margins here.
-// The goal is for the editor to look more like the front-end.
-.editor-block-list__layout .editor-block-list__block[data-type="core/columns"] > .editor-block-list__block-edit,
-.editor-block-list__layout .editor-block-list__block[data-type="core/column"] > .editor-block-list__block-edit {
- margin-top: 0;
- margin-bottom: 0;
-}
-
.wp-block-columns {
display: block;
@@ -55,8 +46,13 @@
// The Column block is a child of the Columns block and is mostly a passthrough container.
// Therefore it shouldn't add additional paddings and margins, so we reset these, and compensate for margins.
// @todo In the future, if a passthrough feature lands, it would be good to apply these rules to such an element in a more generic way.
- margin-top: -$block-padding;
- margin-bottom: -$block-padding;
+ margin-top: -$block-padding - $block-padding;
+ margin-bottom: -$block-padding - $block-padding;
+
+ > .editor-block-list__block-edit {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
// On mobile, only a single column is shown, so match adjacent block paddings.
padding-left: 0;
@@ -130,3 +126,13 @@
}
}
}
+
+// In absence of making the individual columns resizable, we prevent them from being clickable.
+// This makes them less fiddly. This will be revisited as the interface is refined.
+.wp-block-columns [data-type="core/column"] {
+ pointer-events: none;
+}
+
+:not(.components-disabled) > .wp-block-columns > .editor-inner-blocks > .editor-block-list__layout > [data-type="core/column"] > .editor-block-list__block-edit > .editor-inner-blocks {
+ pointer-events: all;
+}
diff --git a/packages/components/src/disabled/style.scss b/packages/components/src/disabled/style.scss
index 8ca29bb801e15..77e0dab63a0b8 100644
--- a/packages/components/src/disabled/style.scss
+++ b/packages/components/src/disabled/style.scss
@@ -10,4 +10,9 @@
bottom: 0;
left: 0;
}
+
+ // Also make nested blocks unselectable.
+ * {
+ pointer-events: none;
+ }
}
diff --git a/packages/editor/src/components/block-list/style.scss b/packages/editor/src/components/block-list/style.scss
index c66a202741175..28632c0eda60a 100644
--- a/packages/editor/src/components/block-list/style.scss
+++ b/packages/editor/src/components/block-list/style.scss
@@ -57,14 +57,9 @@
margin-right: -$block-padding;
}
- // Adjust the spacing of the appender to match text blocks.
- .editor-default-block-appender > .editor-default-block-appender__content {
- margin-top: $block-padding * 2 + $block-spacing;
- }
-
// Space every block, and the default appender, using margins.
// This allows margins to collapse, which gives a better representation of how it looks on the frontend.
- > .editor-default-block-appender__content,
+ .editor-default-block-appender > .editor-default-block-appender__content,
> .editor-block-list__block > .editor-block-list__block-edit,
> .editor-block-list__layout > .editor-block-list__block > .editor-block-list__block-edit {
margin-top: $block-padding * 2 + $block-spacing;
From 24e37f1dc6cca928e2a82eedd9df0a0efdc83d67 Mon Sep 17 00:00:00 2001
From: Maedah Batool
Date: Fri, 9 Nov 2018 13:44:18 +0500
Subject: [PATCH 058/106] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20Grammatical=20Err?=
=?UTF-8?q?or=20(#11656)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fix a tiny-miny grammatical error in the CONTRIBUTORS.md file.
---
CONTRIBUTORS.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 3b526bb045107..e3dd47a328047 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -2,7 +2,7 @@
Gutenberg is built by many contributors and volunteers. Thanks to all of them for their work!
-This list is manually curated to include valuable contributions by volunteers that do not include code, such as user testing, providing feedback, or mockups. Please edit this list to include new contributors as they come in. There is no particular order to this list. If you or someone else were omitted from this list, we assure you that was not intentional. Please let us know and we'll add you. For volunteers who contributed their translations, your names are listed on [WordPress Translate site here](https://translate.wordpress.org/projects/wp-plugins/gutenberg/contributors).
+This list is manually curated to include valuable contributions by volunteers that do not include code, such as user testing, providing feedback, or mockups. Please edit this list to include new contributors as they come in. There is no particular order to this list. If you or someone else was omitted from this list, we assure you that was not intentional. Please let us know and we'll add you. For volunteers who contributed their translations, your names are listed on [WordPress Translate site here](https://translate.wordpress.org/projects/wp-plugins/gutenberg/contributors).
| GitHub Username | WordPress.org Username|
| --------------- | --------------------- |
From 5531bd14abfe6b1366e3aa913f98311f824e4ff8 Mon Sep 17 00:00:00 2001
From: Pinar Olguc
Date: Fri, 9 Nov 2018 12:01:22 +0300
Subject: [PATCH 059/106] Update media url only if it exists in image
block(gb-mobile) (#11635)
* Update media url only if it exists
This is for handling situations where user just presses Cancel on the media picker.
We don't want the image block to update in that case.
* Fix lint issues
---
packages/block-library/src/image/edit.native.js | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js
index d339d077eb572..ab7af710e4e6d 100644
--- a/packages/block-library/src/image/edit.native.js
+++ b/packages/block-library/src/image/edit.native.js
@@ -22,7 +22,11 @@ export default function ImageEdit( props ) {
const onMediaLibraryPress = () => {
// Call onMediaLibraryPress from the Native<->RN bridge. It should trigger an image picker from
// the WordPress media library and call the provided callback to set the image URL.
- RNReactNativeGutenbergBridge.onMediaLibraryPress( ( mediaUrl ) => setAttributes( { url: mediaUrl } ) );
+ RNReactNativeGutenbergBridge.onMediaLibraryPress( ( mediaUrl ) => {
+ if ( mediaUrl ) {
+ setAttributes( { url: mediaUrl } );
+ }
+ } );
};
if ( ! url ) {
From 448d9f47abf6d78728aec14cf3acf262de6b78c1 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Fri, 9 Nov 2018 10:54:49 +0100
Subject: [PATCH 060/106] Update the block styles registration to avoid timing
issues (#11532)
---
docs/data/data-core-blocks.md | 31 +++++++
packages/blocks/CHANGELOG.md | 3 +-
packages/blocks/src/api/registration.js | 29 +------
packages/blocks/src/api/test/registration.js | 86 -------------------
packages/blocks/src/store/actions.js | 32 +++++++
packages/blocks/src/store/reducer.js | 50 ++++++++++-
packages/blocks/src/store/selectors.js | 12 +++
packages/blocks/src/store/test/reducer.js | 84 +++++++++++++++++-
packages/editor/CHANGELOG.md | 6 ++
.../src/components/block-inspector/index.js | 9 +-
.../src/components/block-styles/index.js | 11 +--
.../src/components/block-switcher/index.js | 16 ++--
12 files changed, 239 insertions(+), 130 deletions(-)
diff --git a/docs/data/data-core-blocks.md b/docs/data/data-core-blocks.md
index 9a2524905fa40..aea753fad4757 100644
--- a/docs/data/data-core-blocks.md
+++ b/docs/data/data-core-blocks.md
@@ -23,6 +23,19 @@ Returns a block type by name.
Block Type.
+### getBlockStyles
+
+Returns block styles by block name.
+
+*Parameters*
+
+ * state: Data state.
+ * name: Block type name.
+
+*Returns*
+
+Block Styles.
+
### getCategories
Returns all the available categories.
@@ -161,6 +174,24 @@ Returns an action object used to remove a registered block type.
* names: Block name.
+### addBlockStyles
+
+Returns an action object used in signalling that new block styles have been added.
+
+*Parameters*
+
+ * blockName: Block name.
+ * styles: Block styles.
+
+### removeBlockStyles
+
+Returns an action object used in signalling that block styles have been removed.
+
+*Parameters*
+
+ * blockName: Block name.
+ * styleNames: Block style names.
+
### setDefaultBlockName
Returns an action object used to set the default block name.
diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md
index b3eaf4372a0de..d0e0f71f43e36 100644
--- a/packages/blocks/CHANGELOG.md
+++ b/packages/blocks/CHANGELOG.md
@@ -3,6 +3,7 @@
### New feature
- `getBlockAttributes`, `getBlockTransforms`, `getSaveContent`, `getSaveElement` and `isValidBlockContent` methods can now take also block's name as the first param ([#11490](https://github.com/WordPress/gutenberg/pull/11490)). Passing a block's type object continues to work as before.
+- `registerBlockStyles` and `unregisterBlockStyles` can be triggered at any moment (before or after block registration).
## 5.2.0 (2018-11-09)
@@ -16,7 +17,7 @@
## 5.1.0 (2018-10-30)
-### New feature
+### New features
- `isValidBlockContent` function has been added ([#10891](https://github.com/WordPress/gutenberg/pull/10891)).
diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js
index 8c56373da24b4..d0d21a897c526 100644
--- a/packages/blocks/src/api/registration.js
+++ b/packages/blocks/src/api/registration.js
@@ -3,12 +3,12 @@
/**
* External dependencies
*/
-import { get, isFunction, some, reject } from 'lodash';
+import { get, isFunction, some } from 'lodash';
/**
* WordPress dependencies
*/
-import { applyFilters, addFilter } from '@wordpress/hooks';
+import { applyFilters } from '@wordpress/hooks';
import { select, dispatch } from '@wordpress/data';
/**
@@ -329,19 +329,7 @@ export const hasChildBlocksWithInserterSupport = ( blockName ) => {
* @param {Object} styleVariation Object containing `name` which is the class name applied to the block and `label` which identifies the variation to the user.
*/
export const registerBlockStyle = ( blockName, styleVariation ) => {
- addFilter( 'blocks.registerBlockType', `${ blockName }/${ styleVariation.name }`, ( settings, name ) => {
- if ( blockName !== name ) {
- return settings;
- }
-
- return {
- ...settings,
- styles: [
- ...get( settings, [ 'styles' ], [] ),
- styleVariation,
- ],
- };
- } );
+ dispatch( 'core/blocks' ).addBlockStyles( blockName, styleVariation );
};
/**
@@ -351,14 +339,5 @@ export const registerBlockStyle = ( blockName, styleVariation ) => {
* @param {string} styleVariationName Name of class applied to the block.
*/
export const unregisterBlockStyle = ( blockName, styleVariationName ) => {
- addFilter( 'blocks.registerBlockType', `${ blockName }/${ styleVariationName }/unregister`, ( settings, name ) => {
- if ( blockName !== name ) {
- return settings;
- }
-
- return {
- ...settings,
- styles: reject( get( settings, [ 'styles' ], [] ), { name: styleVariationName } ),
- };
- } );
+ dispatch( 'core/blocks' ).removeBlockStyles( blockName, styleVariationName );
};
diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js
index 047fa53a4fb47..e881943d5168e 100644
--- a/packages/blocks/src/api/test/registration.js
+++ b/packages/blocks/src/api/test/registration.js
@@ -28,8 +28,6 @@ import {
hasBlockSupport,
isReusableBlock,
unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase
- registerBlockStyle,
- unregisterBlockStyle,
} from '../registration';
describe( 'blocks', () => {
@@ -584,88 +582,4 @@ describe( 'blocks', () => {
expect( isReusableBlock( block ) ).toBe( false );
} );
} );
-
- describe( 'registerBlockStyle', () => {
- afterEach( () => {
- removeFilter( 'blocks.registerBlockType', 'my-plugin/block-without-styles/big' );
- removeFilter( 'blocks.registerBlockType', 'my-plugin/block-without-styles/small' );
- } );
-
- it( 'should add styles', () => {
- registerBlockStyle( 'my-plugin/block-without-styles', { name: 'big', label: 'Big style' } );
- const settings = registerBlockType( 'my-plugin/block-without-styles', defaultBlockSettings );
-
- expect( settings.styles ).toEqual( [
- { name: 'big', label: 'Big style' },
- ] );
- } );
-
- it( 'should accumulate styles', () => {
- registerBlockStyle( 'my-plugin/block-without-styles', { name: 'small', label: 'Small style' } );
- registerBlockStyle( 'my-plugin/block-without-styles', { name: 'big', label: 'Big style' } );
- const settings = registerBlockType( 'my-plugin/block-without-styles', {
- ...defaultBlockSettings,
- styles: [ { name: 'normal', label: 'Normal style' } ],
- } );
-
- expect( settings.styles ).toEqual( [
- { name: 'normal', label: 'Normal style' },
- { name: 'small', label: 'Small style' },
- { name: 'big', label: 'Big style' },
- ] );
- } );
- } );
-
- describe( 'unregisterBlockStyle', () => {
- afterEach( () => {
- removeFilter( 'blocks.registerBlockType', 'my-plugin/block-with-styles/big/unregister' );
- removeFilter( 'blocks.registerBlockType', 'my-plugin/block-with-styles/small/unregister' );
- removeFilter( 'blocks.registerBlockType', 'my-plugin/block-with-styles/big' );
- removeFilter( 'blocks.registerBlockType', 'my-plugin/block-with-styles/small' );
- } );
-
- it( 'should remove styles', () => {
- unregisterBlockStyle( 'my-plugin/block-with-styles', 'big' );
- const settings = registerBlockType( 'my-plugin/block-with-styles', {
- ...defaultBlockSettings,
- styles: [ { name: 'big', label: 'Big style' } ],
- } );
-
- expect( settings.styles ).toEqual( [] );
- } );
-
- it( 'should keep other styles', () => {
- unregisterBlockStyle( 'my-plugin/block-with-styles', 'small' );
- const settings = registerBlockType( 'my-plugin/block-with-styles', {
- ...defaultBlockSettings,
- styles: [
- { name: 'normal', label: 'Normal style' },
- { name: 'small', label: 'Small style' },
- { name: 'big', label: 'Big style' },
- ],
- } );
-
- expect( settings.styles ).toEqual( [
- { name: 'normal', label: 'Normal style' },
- { name: 'big', label: 'Big style' },
- ] );
- } );
-
- it( 'should remove a prior registerBlockStyle', () => {
- registerBlockStyle( 'my-plugin/block-with-styles', { name: 'big', label: 'Big style' } );
- registerBlockStyle( 'my-plugin/block-with-styles', { name: 'small', label: 'Small style' } );
- unregisterBlockStyle( 'my-plugin/block-with-styles', 'big' );
- const settings = registerBlockType( 'my-plugin/block-with-styles', {
- ...defaultBlockSettings,
- styles: [
- { name: 'normal', label: 'Normal style' },
- ],
- } );
-
- expect( settings.styles ).toEqual( [
- { name: 'normal', label: 'Normal style' },
- { name: 'small', label: 'Small style' },
- ] );
- } );
- } );
} );
diff --git a/packages/blocks/src/store/actions.js b/packages/blocks/src/store/actions.js
index c2da126f4458d..bef332bb2b5fc 100644
--- a/packages/blocks/src/store/actions.js
+++ b/packages/blocks/src/store/actions.js
@@ -31,6 +31,38 @@ export function removeBlockTypes( names ) {
};
}
+/**
+ * Returns an action object used in signalling that new block styles have been added.
+ *
+ * @param {string} blockName Block name.
+ * @param {Array|Object} styles Block styles.
+ *
+ * @return {Object} Action object.
+ */
+export function addBlockStyles( blockName, styles ) {
+ return {
+ type: 'ADD_BLOCK_STYLES',
+ styles: castArray( styles ),
+ blockName,
+ };
+}
+
+/**
+ * Returns an action object used in signalling that block styles have been removed.
+ *
+ * @param {string} blockName Block name.
+ * @param {Array|string} styleNames Block style names.
+ *
+ * @return {Object} Action object.
+ */
+export function removeBlockStyles( blockName, styleNames ) {
+ return {
+ type: 'REMOVE_BLOCK_STYLES',
+ styleNames: castArray( styleNames ),
+ blockName,
+ };
+}
+
/**
* Returns an action object used to set the default block name.
*
diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js
index e1ed669e0b217..0ce2c1e88ef3f 100644
--- a/packages/blocks/src/store/reducer.js
+++ b/packages/blocks/src/store/reducer.js
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import { keyBy, omit } from 'lodash';
+import { keyBy, omit, mapValues, get, uniqBy, filter, map } from 'lodash';
/**
* WordPress dependencies
@@ -34,7 +34,10 @@ export function blockTypes( state = {}, action ) {
case 'ADD_BLOCK_TYPES':
return {
...state,
- ...keyBy( action.blockTypes, 'name' ),
+ ...keyBy(
+ map( action.blockTypes, ( blockType ) => omit( blockType, 'styles ' ) ),
+ 'name'
+ ),
};
case 'REMOVE_BLOCK_TYPES':
return omit( state, action.names );
@@ -43,6 +46,47 @@ export function blockTypes( state = {}, action ) {
return state;
}
+/**
+ * Reducer managing the block style variations.
+ *
+ * @param {Object} state Current state.
+ * @param {Object} action Dispatched action.
+ *
+ * @return {Object} Updated state.
+ */
+export function blockStyles( state = {}, action ) {
+ switch ( action.type ) {
+ case 'ADD_BLOCK_TYPES':
+ return {
+ ...state,
+ ...mapValues( keyBy( action.blockTypes, 'name' ), ( blockType ) => {
+ return uniqBy( [
+ ...get( blockType, [ 'styles' ], [] ),
+ ...get( state, [ blockType.name ], [] ),
+ ], ( style ) => style.name );
+ } ),
+ };
+ case 'ADD_BLOCK_STYLES':
+ return {
+ ...state,
+ [ action.blockName ]: uniqBy( [
+ ...get( state, [ action.blockName ], [] ),
+ ...( action.styles ),
+ ], ( style ) => style.name ),
+ };
+ case 'REMOVE_BLOCK_STYLES':
+ return {
+ ...state,
+ [ action.blockName ]: filter(
+ get( state, [ action.blockName ], [] ),
+ ( style ) => action.styleNames.indexOf( style.name ) === -1,
+ ),
+ };
+ }
+
+ return state;
+}
+
/**
* Higher-order Reducer creating a reducer keeping track of given block name.
*
@@ -68,7 +112,6 @@ export function createBlockNameSetterReducer( setActionType ) {
}
export const defaultBlockName = createBlockNameSetterReducer( 'SET_DEFAULT_BLOCK_NAME' );
-
export const freeformFallbackBlockName = createBlockNameSetterReducer( 'SET_FREEFORM_FALLBACK_BLOCK_NAME' );
export const unregisteredFallbackBlockName = createBlockNameSetterReducer( 'SET_UNREGISTERED_FALLBACK_BLOCK_NAME' );
@@ -90,6 +133,7 @@ export function categories( state = DEFAULT_CATEGORIES, action ) {
export default combineReducers( {
blockTypes,
+ blockStyles,
defaultBlockName,
freeformFallbackBlockName,
unregisteredFallbackBlockName,
diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js
index 6f36f8fed2992..9ae3373cbd5dc 100644
--- a/packages/blocks/src/store/selectors.js
+++ b/packages/blocks/src/store/selectors.js
@@ -30,6 +30,18 @@ export function getBlockType( state, name ) {
return state.blockTypes[ name ];
}
+/**
+ * Returns block styles by block name.
+ *
+ * @param {Object} state Data state.
+ * @param {string} name Block type name.
+ *
+ * @return {Array?} Block Styles.
+ */
+export function getBlockStyles( state, name ) {
+ return state.blockStyles[ name ];
+}
+
/**
* Returns all the available categories.
*
diff --git a/packages/blocks/src/store/test/reducer.js b/packages/blocks/src/store/test/reducer.js
index 9c3e6c5d1d1dc..dcd2a15c18e08 100644
--- a/packages/blocks/src/store/test/reducer.js
+++ b/packages/blocks/src/store/test/reducer.js
@@ -7,6 +7,7 @@ import deepFreeze from 'deep-freeze';
* Internal dependencies
*/
import {
+ blockStyles,
blockTypes,
categories,
defaultBlockName,
@@ -20,7 +21,7 @@ describe( 'blockTypes', () => {
expect( blockTypes( undefined, {} ) ).toEqual( {} );
} );
- it( 'should add add a new block type', () => {
+ it( 'should add a new block type', () => {
const original = deepFreeze( {
'core/paragraph': { name: 'core/paragraph' },
} );
@@ -53,6 +54,87 @@ describe( 'blockTypes', () => {
} );
} );
+describe( 'blockStyles', () => {
+ it( 'should return an empty object as default state', () => {
+ expect( blockStyles( undefined, {} ) ).toEqual( {} );
+ } );
+
+ it( 'should add a new block styles', () => {
+ const original = deepFreeze( {} );
+
+ let state = blockStyles( original, {
+ type: 'ADD_BLOCK_STYLES',
+ blockName: 'core/image',
+ styles: [ { name: 'fancy' } ],
+ } );
+
+ expect( state ).toEqual( {
+ 'core/image': [
+ { name: 'fancy' },
+ ],
+ } );
+
+ state = blockStyles( state, {
+ type: 'ADD_BLOCK_STYLES',
+ blockName: 'core/image',
+ styles: [ { name: 'lightbox' } ],
+ } );
+
+ expect( state ).toEqual( {
+ 'core/image': [
+ { name: 'fancy' },
+ { name: 'lightbox' },
+ ],
+ } );
+ } );
+
+ it( 'should add block styles when adding a block', () => {
+ const original = deepFreeze( {
+ 'core/image': [
+ { name: 'fancy' },
+ ],
+ } );
+
+ const state = blockStyles( original, {
+ type: 'ADD_BLOCK_TYPES',
+ blockTypes: [ {
+ name: 'core/image',
+ styles: [
+ { name: 'original' },
+ ],
+ } ],
+ } );
+
+ expect( state ).toEqual( {
+ 'core/image': [
+ { name: 'original' },
+ { name: 'fancy' },
+ ],
+ } );
+ } );
+
+ it( 'should remove block styles', () => {
+ const original = deepFreeze( {
+ 'core/image': [
+ { name: 'fancy' },
+ { name: 'lightbox' },
+ ],
+ } );
+
+ const state = blockStyles( original, {
+ type: 'REMOVE_BLOCK_STYLES',
+ blockName: 'core/image',
+ styleNames: [ 'fancy' ],
+ } );
+
+ expect( state ).toEqual( {
+ 'core/image': [
+ { name: 'lightbox' },
+ ],
+ } );
+ } );
+} );
+
describe( 'defaultBlockName', () => {
it( 'should return null as default state', () => {
expect( defaultBlockName( undefined, {} ) ).toBeNull();
diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md
index 38da9bc69d69a..9379ec67de996 100644
--- a/packages/editor/CHANGELOG.md
+++ b/packages/editor/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 6.2.1 (Unreleased)
+
+### Polish
+
+- Reactive block styles.
+
## 6.2.0 (2018-11-09)
### New Features
diff --git a/packages/editor/src/components/block-inspector/index.js b/packages/editor/src/components/block-inspector/index.js
index 7f2fc1c4654f8..1b51fedac4a1a 100644
--- a/packages/editor/src/components/block-inspector/index.js
+++ b/packages/editor/src/components/block-inspector/index.js
@@ -21,7 +21,7 @@ import InspectorControls from '../inspector-controls';
import InspectorAdvancedControls from '../inspector-advanced-controls';
import BlockStyles from '../block-styles';
-const BlockInspector = ( { selectedBlock, blockType, count } ) => {
+const BlockInspector = ( { selectedBlock, blockType, count, hasBlockStyles } ) => {
if ( count > 1 ) {
return { __( 'Coming Soon' ) };
}
@@ -46,7 +46,7 @@ const BlockInspector = ( { selectedBlock, blockType, count } ) => {
{ blockType.description }