From fa549431df8ea289d6c95889887baae007c3efa9 Mon Sep 17 00:00:00 2001 From: Jorge Date: Sun, 4 Mar 2018 18:02:05 +0000 Subject: [PATCH] Implemented nesting in cover image block. --- blocks/inner-blocks/index.js | 4 +- blocks/library/cover-image/editor.scss | 10 +- blocks/library/cover-image/index.js | 79 ++++-------- blocks/library/cover-image/style.scss | 51 ++------ blocks/library/paragraph/editor.scss | 4 - blocks/test/fixtures/core__cover-image.html | 12 +- blocks/test/fixtures/core__cover-image.json | 27 +++- .../fixtures/core__cover-image.parsed.json | 17 ++- .../core__cover-image.serialized.html | 8 +- editor/components/block-list/block.js | 2 +- .../test/__snapshots__/index.js.snap | 6 +- .../inserter-with-shortcuts/index.js | 16 +-- editor/components/inserter/index.js | 39 ++++-- editor/components/inserter/menu.js | 8 +- editor/store/actions.js | 8 ++ editor/store/reducer.js | 37 ++++++ editor/store/selectors.js | 37 ++++++ editor/utils/block-list.js | 115 ++++++++++++++---- 18 files changed, 309 insertions(+), 171 deletions(-) diff --git a/blocks/inner-blocks/index.js b/blocks/inner-blocks/index.js index 37ae97e13c71a7..6b1d56c44932db 100644 --- a/blocks/inner-blocks/index.js +++ b/blocks/inner-blocks/index.js @@ -3,8 +3,8 @@ */ import { withContext } from '@wordpress/components'; -function InnerBlocks( { BlockList, layouts } ) { - return ; +function InnerBlocks( { BlockList, layouts, allowedBlocks, template } ) { + return ; } InnerBlocks = withContext( 'BlockList' )()( InnerBlocks ); diff --git a/blocks/library/cover-image/editor.scss b/blocks/library/cover-image/editor.scss index 29db22108e5d96..088bb903e762db 100644 --- a/blocks/library/cover-image/editor.scss +++ b/blocks/library/cover-image/editor.scss @@ -22,10 +22,16 @@ } &.has-left-content .block-rich-text__inline-toolbar { - justify-content: flex-start; + display: inline-block; } &.has-right-content .block-rich-text__inline-toolbar{ - justify-content: flex-end; + display: inline-block; } + + .editor-block-list__layout { + width: 100%; + } + + } diff --git a/blocks/library/cover-image/index.js b/blocks/library/cover-image/index.js index 502f72655d3efd..c5f9b5998af52a 100644 --- a/blocks/library/cover-image/index.js +++ b/blocks/library/cover-image/index.js @@ -1,12 +1,7 @@ -/** - * External dependencies - */ -import { isEmpty } from 'lodash'; - /** * WordPress dependencies */ -import { IconButton, PanelBody, RangeControl, ToggleControl, Toolbar } from '@wordpress/components'; +import { IconButton, RangeControl, ToggleControl, Toolbar } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import classnames from 'classnames'; @@ -16,32 +11,22 @@ import classnames from 'classnames'; import './editor.scss'; import './style.scss'; import { createBlock } from '../../api'; -import RichText from '../../rich-text'; -import AlignmentToolbar from '../../alignment-toolbar'; import MediaUpload from '../../media-upload'; import ImagePlaceholder from '../../image-placeholder'; import BlockControls from '../../block-controls'; import BlockAlignmentToolbar from '../../block-alignment-toolbar'; import InspectorControls from '../../inspector-controls'; +import InnerBlocks from '../../inner-blocks'; const validAlignments = [ 'left', 'center', 'right', 'wide', 'full' ]; const blockAttributes = { - title: { - type: 'array', - source: 'children', - selector: 'p', - }, url: { type: 'string', }, align: { type: 'string', }, - contentAlign: { - type: 'string', - default: 'center', - }, id: { type: 'number', }, @@ -97,7 +82,7 @@ export const settings = { }, edit( { attributes, setAttributes, isSelected, className } ) { - const { url, title, align, contentAlign, id, hasParallax, dimRatio } = attributes; + const { align, url, id, hasParallax, dimRatio } = attributes; const updateAlignment = ( nextAlign ) => setAttributes( { align: nextAlign } ); const onSelectImage = ( media ) => setAttributes( { url: media.url, id: media.id } ); const toggleParallax = () => setAttributes( { hasParallax: ! hasParallax } ); @@ -106,7 +91,6 @@ export const settings = { const style = backgroundImageStyles( url ); const classes = classnames( className, - contentAlign !== 'center' && `has-${ contentAlign }-content`, dimRatioToClass( dimRatio ), { 'has-background-dim': dimRatio !== 0, @@ -114,22 +98,12 @@ export const settings = { } ); - const alignmentToolbar = ( - { - setAttributes( { contentAlign: nextAlign } ); - } } - /> - ); const controls = isSelected && [ - - { alignmentToolbar } - - { alignmentToolbar } - , ]; if ( ! url ) { - const hasTitle = ! isEmpty( title ); - const icon = hasTitle ? undefined : 'format-image'; - const label = hasTitle ? ( - setAttributes( { title: value } ) } - isSelected={ isSelected } - inlineToolbar - /> - ) : __( 'Cover Image' ); + const icon = 'format-image'; + const label = __( 'Cover Image' ); return [ controls, @@ -196,23 +158,25 @@ export const settings = { style={ style } className={ classes } > - { title || isSelected ? ( - setAttributes( { title: value } ) } - isSelected={ isSelected } - inlineToolbar +
+ - ) : null } +
, ]; }, save( { attributes, className } ) { - const { url, title, hasParallax, dimRatio, align, contentAlign } = attributes; + const { url, hasParallax, dimRatio, align } = attributes; const style = backgroundImageStyles( url ); const classes = classnames( className, @@ -220,16 +184,15 @@ export const settings = { { 'has-background-dim': dimRatio !== 0, 'has-parallax': hasParallax, - [ `has-${ contentAlign }-content` ]: contentAlign !== 'center', }, align ? `align${ align }` : null, ); return (
- { title && title.length > 0 && ( -

{ title }

- ) } +
+ +
); }, diff --git a/blocks/library/cover-image/style.scss b/blocks/library/cover-image/style.scss index f9becd0e18eaad..060d147b0a16b9 100644 --- a/blocks/library/cover-image/style.scss +++ b/blocks/library/cover-image/style.scss @@ -6,46 +6,7 @@ margin: 0 0 1.5em 0; display: flex; justify-content: center; - align-items: center; - - &.has-left-content { - justify-content: flex-start; - - h2, - .wp-block-cover-image-text { - margin-left: 0; - text-align: left; - } - } - - &.has-right-content { - justify-content: flex-end; - - h2, - .wp-block-cover-image-text { - margin-right: 0; - text-align: right; - } - } - - h2, - .wp-block-cover-image-text { - color: white; - font-size: 2em; - line-height: 1.25; - z-index: 1; - margin-bottom: 0; - max-width: $content-width; - padding: $block-padding; - text-align: center; - - a, - a:hover, - a:focus, - a:active { - color: white; - } - } + flex-direction: column; &.has-parallax { background-attachment: fixed; @@ -71,4 +32,14 @@ height: inherit; } + .wp-block-cover-image__inner-container { + width: 100%; + p { + margin-top: 0; + margin-bottom: 0; + } + } + + + } diff --git a/blocks/library/paragraph/editor.scss b/blocks/library/paragraph/editor.scss index e5fe10296b7f15..cc49ca86b80702 100644 --- a/blocks/library/paragraph/editor.scss +++ b/blocks/library/paragraph/editor.scss @@ -1,7 +1,3 @@ -.editor-block-list__block:not( .is-multi-selected ) .wp-block-paragraph { - background: white; -} - .blocks-font-size__main { display: flex; justify-content: space-between; diff --git a/blocks/test/fixtures/core__cover-image.html b/blocks/test/fixtures/core__cover-image.html index 86b5915701b0b0..1b871458a15b70 100644 --- a/blocks/test/fixtures/core__cover-image.html +++ b/blocks/test/fixtures/core__cover-image.html @@ -1,5 +1,9 @@ - -
-

Guten Berg!

+ +
+
+ +

Paragraph 1

+ +
- + diff --git a/blocks/test/fixtures/core__cover-image.json b/blocks/test/fixtures/core__cover-image.json index 7f12304be6f8ab..8451793404e77c 100644 --- a/blocks/test/fixtures/core__cover-image.json +++ b/blocks/test/fixtures/core__cover-image.json @@ -4,15 +4,30 @@ "name": "core/cover-image", "isValid": true, "attributes": { - "title": [ - "Guten Berg!" - ], "url": "https://cldup.com/uuUqE_dXzy.jpg", - "contentAlign": "center", + "id": 8398, "hasParallax": false, "dimRatio": 40 }, - "innerBlocks": [], - "originalContent": "
\n

Guten Berg!

\n
" + "innerBlocks": [ + { + "uid": "_uid_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "content": [ + "Paragraph 1" + ], + "align": "center", + "dropCap": false, + "placeholder": "Write title…", + "textColor": "#fff", + "fontSize": "large" + }, + "innerBlocks": [], + "originalContent": "

Paragraph 1

" + } + ], + "originalContent": "
\n
\n\t\t\n
\n
" } ] diff --git a/blocks/test/fixtures/core__cover-image.parsed.json b/blocks/test/fixtures/core__cover-image.parsed.json index 25808e4279256f..5b285f3d447254 100644 --- a/blocks/test/fixtures/core__cover-image.parsed.json +++ b/blocks/test/fixtures/core__cover-image.parsed.json @@ -3,10 +3,23 @@ "blockName": "core/cover-image", "attrs": { "url": "https://cldup.com/uuUqE_dXzy.jpg", + "id": 8398, "dimRatio": 40 }, - "innerBlocks": [], - "innerHTML": "\n
\n

Guten Berg!

\n
\n" + "innerBlocks": [ + { + "blockName": "core/paragraph", + "attrs": { + "align": "center", + "placeholder": "Write title…", + "textColor": "#fff", + "fontSize": "large" + }, + "innerBlocks": [], + "innerHTML": "\n\t\t

Paragraph 1

\n\t\t" + } + ], + "innerHTML": "\n
\n
\n\t\t\n
\n
\n" }, { "attrs": {}, diff --git a/blocks/test/fixtures/core__cover-image.serialized.html b/blocks/test/fixtures/core__cover-image.serialized.html index 160d3aa203dbf1..bf4a6fb9d86e74 100644 --- a/blocks/test/fixtures/core__cover-image.serialized.html +++ b/blocks/test/fixtures/core__cover-image.serialized.html @@ -1,5 +1,9 @@ - +
-

Guten Berg!

+
+ +

Paragraph 1

+ +
diff --git a/editor/components/block-list/block.js b/editor/components/block-list/block.js index ec4059d9e1924d..f71047aaa6abff 100644 --- a/editor/components/block-list/block.js +++ b/editor/components/block-list/block.js @@ -571,7 +571,7 @@ export class BlockListBlock extends Component { { showSideInserter && (
- +
-
@@ -60,7 +60,7 @@ exports[`DefaultBlockAppender should match snapshot 1`] = ` value="Write your story" /> -
@@ -84,7 +84,7 @@ exports[`DefaultBlockAppender should optionally show without prompt 1`] = ` value="" /> - diff --git a/editor/components/inserter-with-shortcuts/index.js b/editor/components/inserter-with-shortcuts/index.js index dbb662f7d84277..0f8804e05067dc 100644 --- a/editor/components/inserter-with-shortcuts/index.js +++ b/editor/components/inserter-with-shortcuts/index.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import { connect } from 'react-redux'; import { filter, isEmpty } from 'lodash'; /** @@ -11,13 +10,12 @@ import { BlockIcon, createBlock, getDefaultBlockName } from '@wordpress/blocks'; import { compose } from '@wordpress/element'; import { IconButton, withContext } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { withDispatch } from '@wordpress/data'; +import { withDispatch, withSelect } from '@wordpress/data'; /** * Internal dependencies */ import './style.scss'; -import { getFrecentInserterItems } from '../../store/selectors'; function InserterWithShortcuts( { items, isLocked, onInsert } ) { if ( isLocked ) { @@ -54,11 +52,13 @@ export default compose( enabledBlockTypes: blockTypes, }; } ), - connect( - ( state, { enabledBlockTypes } ) => ( { - items: getFrecentInserterItems( state, enabledBlockTypes, 4 ), - } ) - ), + withSelect( ( select, { enabledBlockTypes, rootUID } ) => { + const { getFrecentInserterItems, getSupportedBlocks } = select( 'core/editor' ); + const supportedBlocks = getSupportedBlocks( rootUID, enabledBlockTypes ); + return { + items: getFrecentInserterItems( supportedBlocks, 4 ), + }; + } ), withDispatch( ( dispatch, ownProps ) => { const { uid, rootUID, layout } = ownProps; diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js index d8fb08ec6d18e8..43b092b396d780 100644 --- a/editor/components/inserter/index.js +++ b/editor/components/inserter/index.js @@ -87,11 +87,32 @@ class Inserter extends Component { } export default compose( [ - withSelect( ( select ) => ( { - title: select( 'core/editor' ).getEditedPostAttribute( 'title' ), - insertionPoint: select( 'core/editor' ).getBlockInsertionPoint(), - selectedBlock: select( 'core/editor' ).getSelectedBlock(), - } ) ), + withContext( 'editor' )( ( settings ) => { + const { blockTypes, templateLock } = settings; + + return { + enabledBlockTypes: blockTypes, + isLocked: !! templateLock, + }; + } ), + withSelect( ( select, { enabledBlockTypes } ) => { + const { + getEditedPostAttribute, + getBlockInsertionPoint, + getSelectedBlock, + getSupportedBlocks, + } = select( 'core/editor' ); + + const insertionPoint = getBlockInsertionPoint(); + const { rootUID } = insertionPoint; + const supportedBlocks = getSupportedBlocks( rootUID, enabledBlockTypes ); + return { + title: getEditedPostAttribute( 'title' ), + insertionPoint, + selectedBlock: getSelectedBlock(), + hasSupportedBlocks: true === supportedBlocks || ! isEmpty( supportedBlocks ), + }; + } ), withDispatch( ( dispatch, ownProps ) => ( { showInsertionPoint: dispatch( 'core/editor' ).showInsertionPoint, hideInsertionPoint: dispatch( 'core/editor' ).hideInsertionPoint, @@ -106,12 +127,4 @@ export default compose( [ return dispatch( 'core/editor' ).insertBlock( insertedBlock, index, rootUID ); }, } ) ), - withContext( 'editor' )( ( settings ) => { - const { blockTypes, templateLock } = settings; - - return { - hasSupportedBlocks: true === blockTypes || ! isEmpty( blockTypes ), - isLocked: !! templateLock, - }; - } ), ] )( Inserter ); diff --git a/editor/components/inserter/menu.js b/editor/components/inserter/menu.js index 669cad3c2eefc3..3cba8ecd5671e1 100644 --- a/editor/components/inserter/menu.js +++ b/editor/components/inserter/menu.js @@ -35,7 +35,7 @@ import { keycodes } from '@wordpress/utils'; import './style.scss'; import NoBlocks from './no-blocks'; -import { getInserterItems, getFrecentInserterItems } from '../../store/selectors'; +import { getBlockInsertionPoint, getInserterItems, getFrecentInserterItems, getSupportedBlocks } from '../../store/selectors'; import { fetchReusableBlocks } from '../../store/actions'; import { default as InserterGroup } from './group'; import BlockPreview from '../block-preview'; @@ -349,9 +349,11 @@ export default compose( } ), connect( ( state, ownProps ) => { + const { rootUID } = getBlockInsertionPoint( state ); + const supportedBlocks = getSupportedBlocks( state, rootUID, ownProps.enabledBlockTypes ); return { - items: getInserterItems( state, ownProps.enabledBlockTypes ), - frecentItems: getFrecentInserterItems( state, ownProps.enabledBlockTypes ), + items: getInserterItems( state, supportedBlocks ), + frecentItems: getFrecentInserterItems( state, supportedBlocks ), }; }, { fetchReusableBlocks } diff --git a/editor/store/actions.js b/editor/store/actions.js index dd711abbb9ce61..63246047f35ce1 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -616,3 +616,11 @@ export function insertDefaultBlock( attributes, rootUID, index ) { isProvisional: true, }; } + +export function updateBlockListSettings( id, settings ) { + return { + type: 'UPDATED_BLOCK_LIST_SETTINGS', + id, + settings, + }; +} diff --git a/editor/store/reducer.js b/editor/store/reducer.js index e6d0a9f7aa40d4..7d069861190005 100644 --- a/editor/store/reducer.js +++ b/editor/store/reducer.js @@ -976,6 +976,42 @@ export const reusableBlocks = combineReducers( { }, } ); +/** + * Reducer that for each block uid stores an object that represents its nested nestings. + * E.g: what blocks can be nested inside a block. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export const blockListSettings = ( state = {}, action ) => { + switch ( action.type ) { + // even if the replaced blocks have the same uid our logic should correct the state. + case 'REPLACE_BLOCKS' : + case 'REMOVE_BLOCKS': { + return omit( state, action.uids ); + } + case 'UPDATED_BLOCK_LIST_SETTINGS': { + const { id, settings } = action; + if ( id && ! settings ) { + return omit( state, id ); + } + const blockSettings = state[ id ]; + const updateIsRequired = ! isEqual( blockSettings, settings ); + if ( updateIsRequired ) { + return { + ...state, + [ id ]: { + ...settings, + }, + }; + } + } + } + return state; +}; + export default optimist( combineReducers( { editor, currentPost, @@ -983,6 +1019,7 @@ export default optimist( combineReducers( { blockSelection, provisionalBlockUID, blocksMode, + blockListSettings, isInsertionPointVisible, preferences, saving, diff --git a/editor/store/selectors.js b/editor/store/selectors.js index 6e2ccbf6a34738..96f3652f16ba45 100644 --- a/editor/store/selectors.js +++ b/editor/store/selectors.js @@ -6,6 +6,7 @@ import { first, get, has, + intersection, last, reduce, compact, @@ -1457,3 +1458,39 @@ export function isPublishingPost( state ) { export function getProvisionalBlockUID( state ) { return state.provisionalBlockUID; } + +/** + * Returns the Block List settings of a block if any. + * + * @param {Object} state Editor state. + * @param {?string} uid Block UID. + * + * @return {?Object} Block settings of the block if set. + */ +export function getBlockListSettings( state, uid ) { + return state.blockListSettings[ uid ]; +} + +/** + * Determines the blocks that can be nested inside a given block. Or globally if a block is not specified. + * + * @param {Object} state Global application state. + * @param {?string} uid Block UID. + * @param {string[]|boolean} globallyEnabledBlockTypes Globally enabled block types, or true/false to enable/disable all types. + * + * @return {string[]|boolean} Blocks that can be nested inside the block with the specified uid, or true/false to enable/disable all types. + */ +export function getSupportedBlocks( state, uid, globallyEnabledBlockTypes ) { + if ( ! globallyEnabledBlockTypes ) { + return false; + } + + const supportedNestedBlocks = get( getBlockListSettings( state, uid ), [ 'supportedBlocks' ] ); + if ( ! supportedNestedBlocks ) { + return globallyEnabledBlockTypes; + } + if ( globallyEnabledBlockTypes === true ) { + return supportedNestedBlocks; + } + return intersection( globallyEnabledBlockTypes, supportedNestedBlocks ); +} diff --git a/editor/utils/block-list.js b/editor/utils/block-list.js index 74ea7a3c69ad58..eaf6b9ce2cca3d 100644 --- a/editor/utils/block-list.js +++ b/editor/utils/block-list.js @@ -1,7 +1,16 @@ +/** + * External dependencies + */ +import { isEqual, omit } from 'lodash'; + /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { Component, compose } from '@wordpress/element'; +import { + synchronizeBlocksWithTemplate, +} from '@wordpress/blocks'; +import { withSelect, withDispatch } from '@wordpress/data'; /** * Internal dependencies @@ -32,34 +41,94 @@ const INNER_BLOCK_LIST_CACHE = {}; */ export function createInnerBlockList( uid, renderBlockMenu, showContextualToolbar ) { if ( ! INNER_BLOCK_LIST_CACHE[ uid ] ) { - INNER_BLOCK_LIST_CACHE[ uid ] = [ - // The component class: - class extends Component { - componentWillMount() { - INNER_BLOCK_LIST_CACHE[ uid ][ 1 ]++; + const InnerBlockListComponent = class extends Component { + constructor() { + super( ...arguments ); + this.updateNestedSettings = this.updateNestedSettings.bind( this ); + this.insertTemplateBlocks = this.insertTemplateBlocks.bind( this ); + } + + insertTemplateBlocks( template ) { + const { block, insertBlocks } = this.props; + if ( template && ! block.innerBlocks.length ) { + // synchronizeBlocksWithTemplate( [], template ) parses the template structure, + // and returns/creates the necessary blocks to represent it. + insertBlocks( synchronizeBlocksWithTemplate( [], template ) ); } + } - componentWillUnmount() { - // If, after decrementing the tracking count, there are no - // remaining instances of the component, remove from cache. - if ( ! INNER_BLOCK_LIST_CACHE[ uid ][ 1 ]-- ) { - delete INNER_BLOCK_LIST_CACHE[ uid ]; - } + updateNestedSettings( newSettings ) { + if ( ! isEqual( this.props.blockListSettings, newSettings ) ) { + this.props.updateNestedSettings( newSettings ); } + } - render() { - return ( - - ); + componentWillMount() { + INNER_BLOCK_LIST_CACHE[ uid ][ 1 ]++; + this.updateNestedSettings( { + supportedBlocks: this.props.allowedBlocks, + } ); + this.insertTemplateBlocks( this.props.template ); + } + + componentWillReceiveProps( nextProps ) { + this.updateNestedSettings( { + supportedBlocks: nextProps.allowedBlocks, + } ); + } + + componentWillUnmount() { + // If, after decrementing the tracking count, there are no + // remaining instances of the component, remove from cache. + if ( ! INNER_BLOCK_LIST_CACHE[ uid ][ 1 ]-- ) { + delete INNER_BLOCK_LIST_CACHE[ uid ]; } - }, + } + + render() { + return ( + + ); + } + }; - // A counter tracking active mounted instances: - 0, + const InnerBlockListComponentContainer = compose( + withSelect( ( select ) => { + const { getBlock, getBlockListSettings } = select( 'core/editor' ); + return { + block: getBlock( uid ), + blockListSettings: getBlockListSettings( uid ), + }; + } ), + withDispatch( ( dispatch ) => { + const { insertBlocks, updateBlockListSettings } = dispatch( 'core/editor' ); + return { + insertBlocks( blocks ) { + dispatch( insertBlocks( blocks, undefined, uid ) ); + }, + updateNestedSettings( settings ) { + dispatch( updateBlockListSettings( uid, settings ) ); + }, + }; + } ), + )( InnerBlockListComponent ); + + INNER_BLOCK_LIST_CACHE[ uid ] = [ + InnerBlockListComponentContainer, + 0, // A counter tracking active mounted instances: ]; }