Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editor: Implement the EntityHandlers component and basic template blocks. #17020

Closed
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6854ff6
Editor: Update the store to use Core Data entities.
aduth Jul 25, 2019
f0fb34b
Editor: Fix selector test suites.
epiqueras Aug 6, 2019
b483709
Editor: Fix some legacy selectors and behaviors.
epiqueras Aug 7, 2019
892fd2e
Editor: Fix action tests.
epiqueras Aug 7, 2019
8bcbd14
Editor: Fix remaining broken unit tests.
epiqueras Aug 7, 2019
2cc0e18
Editor: Fix more tests.
epiqueras Aug 8, 2019
a21cb13
Editor: Fix more e2e test behaviors.
epiqueras Aug 8, 2019
1045a01
Editor: Fix preview functionality.
epiqueras Aug 8, 2019
56d0e9e
Core Data: Fix autosaves filtering.
epiqueras Aug 8, 2019
d93d239
Editor: Don't make entity dirty with initial edits.
epiqueras Aug 9, 2019
f1b18ad
Editor: Don't save if the post is not saveable.
epiqueras Aug 9, 2019
28bae58
Core Data: Fix merged edits logic.
epiqueras Aug 9, 2019
afaec5e
Core Data: Fix undo to fit e2e expected behaviors.
epiqueras Aug 9, 2019
bfee37a
Core Data: Handle more change detection and saving flows.
epiqueras Aug 9, 2019
97502af
Block Editor: Fix undo level logic.
epiqueras Aug 9, 2019
029b901
Core Data: Clean up undo reducer comment.
epiqueras Aug 9, 2019
7af50bb
Editor: Implement the `EntityHandlers` component and use it to create…
epiqueras Aug 13, 2019
783e97e
Editor: Refactor custom sources into a simple getter/setter approach.
epiqueras Aug 14, 2019
b56cb43
Editor: Implement the `handles` prop for Entity Handlers.
epiqueras Aug 14, 2019
f8a97bd
Block Library: Make the post block delegate content to its parent and…
epiqueras Aug 15, 2019
0bfcf28
Block Editor: Provide blocks a way to edit entities without relying o…
epiqueras Aug 15, 2019
7245d99
Editor: Make the store multi-entity-kind compatible and implement the…
epiqueras Aug 16, 2019
0f64686
Block Library: Implement post content block.
epiqueras Aug 16, 2019
35bc41c
Block Library: Implement server side rendering for the site title block.
epiqueras Aug 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 70 additions & 19 deletions docs/designers-developers/developers/data/data-core-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ _Related_

<a name="getBlocksForSerialization" href="#getBlocksForSerialization">#</a> **getBlocksForSerialization**

> **Deprecated** since Gutenberg 6.2.0.

Returns a set of blocks which are to be used in consideration of the post's
generated save content.

Expand All @@ -215,6 +217,19 @@ _Related_

- getClientIdsWithDescendants in core/block-editor store.

<a name="getCurrentEntityAttribute" href="#getCurrentEntityAttribute">#</a> **getCurrentEntityAttribute**

Returns an attribute value of the saved entity.

_Parameters_

- _state_ `Object`: Global application state.
- _attributeName_ `string`: Entity attribute name.

_Returns_

- `*`: Entity attribute value.

<a name="getCurrentPost" href="#getCurrentPost">#</a> **getCurrentPost**

Returns the post currently being edited in its last known saved state, not
Expand Down Expand Up @@ -292,6 +307,21 @@ _Returns_

- `string`: Post type.

<a name="getEditedEntityAttribute" href="#getEditedEntityAttribute">#</a> **getEditedEntityAttribute**

Returns a single attribute of the entity being edited, preferring the unsaved
edit if one exists, but falling back to the attribute for the last known
saved state of the entity.

_Parameters_

- _state_ `Object`: Global application state.
- _attributeName_ `string`: Entity attribute name.

_Returns_

- `*`: Entity attribute value.

<a name="getEditedPostAttribute" href="#getEditedPostAttribute">#</a> **getEditedPostAttribute**

Returns a single attribute of the post being edited, preferring the unsaved
Expand All @@ -309,8 +339,7 @@ _Returns_

<a name="getEditedPostContent" href="#getEditedPostContent">#</a> **getEditedPostContent**

Returns the content of the post being edited, preferring raw string edit
before falling back to serialization of block state.
Returns the content of the post being edited.

_Parameters_

Expand Down Expand Up @@ -382,6 +411,22 @@ _Related_

- getGlobalBlockCount in core/block-editor store.

<a name="getHandlesFilteredEdits" href="#getHandlesFilteredEdits">#</a> **getHandlesFilteredEdits**

Returns an object with the edits broken down into a
handled edits object, an edits object that should
be delegated to a parent editor, and the parent's
dispatching function, if any.

_Parameters_

- _state_ `Object`: Editor state.
- _\_edits_ `Object`: The edits, defaults to all of the entity's current edits.

_Returns_

- `Object`: The object with the grouped edits and the parent's dispatching function.

<a name="getInserterItems" href="#getInserterItems">#</a> **getInserterItems**

_Related_
Expand Down Expand Up @@ -740,6 +785,7 @@ Return true if the current post has already been published.
_Parameters_

- _state_ `Object`: Global application state.
- _currentPost_ `?Object`: Explicit current post for bypassing registry selector.

_Returns_

Expand Down Expand Up @@ -1032,18 +1078,23 @@ _Returns_

- `Object`: Action object

<a name="editPost" href="#editPost">#</a> **editPost**
<a name="editEntity" href="#editEntity">#</a> **editEntity**

Returns an action object used in signalling that attributes of the post have
Yields an action object used in signalling that attributes of the entity have
been edited.

_Parameters_

- _edits_ `Object`: Post attributes to edit.
- _edits_ `Object`: Entity attributes to edit.

_Returns_
<a name="editPost" href="#editPost">#</a> **editPost**

- `Object`: Action object.
Yields an action object used in signalling that attributes of the post have
been edited.

_Parameters_

- _edits_ `Object`: Post attributes to edit.

<a name="enablePublishSidebar" href="#enablePublishSidebar">#</a> **enablePublishSidebar**

Expand Down Expand Up @@ -1143,10 +1194,6 @@ _Related_
Returns an action object used in signalling that undo history should
restore last popped state.

_Returns_

- `Object`: Action object.

<a name="refreshPost" href="#refreshPost">#</a> **refreshPost**

Action generator for handling refreshing the current post.
Expand Down Expand Up @@ -1205,10 +1252,6 @@ _Parameters_
- _blocks_ `Array`: Block Array.
- _options_ `?Object`: Optional options.

_Returns_

- `Object`: Action object

<a name="resetPost" href="#resetPost">#</a> **resetPost**

Returns an action object used in signalling that the latest version of the
Expand Down Expand Up @@ -1236,6 +1279,18 @@ _Related_

- selectBlock in core/block-editor store.

<a name="serializeBlocks" href="#serializeBlocks">#</a> **serializeBlocks**

Serializes blocks following backwards compatibility conventions.

_Parameters_

- _blocksForSerialization_ `Array`: The blocks to serialize.

_Returns_

- `string`: The blocks serialization.

<a name="setTemplateValidity" href="#setTemplateValidity">#</a> **setTemplateValidity**

_Related_
Expand Down Expand Up @@ -1322,10 +1377,6 @@ Action generator for trashing the current post in the editor.

Returns an action object used in signalling that undo history should pop.

_Returns_

- `Object`: Action object.

<a name="unlockPostSaving" href="#unlockPostSaving">#</a> **unlockPostSaving**

Returns an action object used to signal that post saving is unlocked.
Expand Down
20 changes: 20 additions & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@ function gutenberg_reregister_core_block_types() {
}
add_action( 'init', 'gutenberg_reregister_core_block_types' );

/**
* Adds new block categories needed by the Gutenberg plugin.
*
* @param array $categories List of block categories.
*
* @return array List of block categories with the new categories added.
*/
function gutenberg_filter_block_categories( $categories ) {
return array_merge(
$categories,
array(
array(
'slug' => 'theme',
'title' => __( 'Theme Blocks' ),
),
)
);
}
add_filter( 'block_categories', 'gutenberg_filter_block_categories' );

/**
* Registers a new block style.
*
Expand Down
7 changes: 7 additions & 0 deletions packages/block-editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => {
replaceBlocks,
toggleSelection,
setNavigationMode,
__unstableMarkLastChangeAsPersistent,
} = dispatch( 'core/block-editor' );

return {
Expand Down Expand Up @@ -755,6 +756,12 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => {
}
},
onReplace( blocks, indexToSelect ) {
if (
blocks.length &&
! isUnmodifiedDefaultBlock( blocks[ blocks.length - 1 ] )
) {
__unstableMarkLastChangeAsPersistent();
}
replaceBlocks( [ ownProps.clientId ], blocks, indexToSelect );
},
onShiftSelection() {
Expand Down
45 changes: 43 additions & 2 deletions packages/block-editor/src/components/inner-blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ class InnerBlocks extends Component {
}

componentDidMount() {
const { innerBlocks } = this.props.block;
const { block, blocks, replaceInnerBlocks } = this.props;
const { innerBlocks } = block;
// only synchronize innerBlocks with template if innerBlocks are empty or a locking all exists
if ( innerBlocks.length === 0 || this.getTemplateLock() === 'all' ) {
this.synchronizeBlocksWithTemplate();
Expand All @@ -55,10 +56,23 @@ class InnerBlocks extends Component {
templateInProcess: false,
} );
}

// Set controlled blocks value from parent, if any.
if ( blocks ) {
replaceInnerBlocks( blocks );
}
}

componentDidUpdate( prevProps ) {
const { template, block } = this.props;
const {
block,
template,
isLastBlockChangePersistent,
resetEditorBlocks,
resetEditorBlocksWithoutUndoLevel,
blocks,
replaceInnerBlocks,
} = this.props;
const { innerBlocks } = block;

this.updateNestedSettings();
Expand All @@ -69,6 +83,31 @@ class InnerBlocks extends Component {
this.synchronizeBlocksWithTemplate();
}
}

// Sync with controlled blocks value from parent, if possible.
if ( this.isSyncingIncomingBlocks === innerBlocks ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this logic is not going to work because innerBlocks will never have the same reference that isSyncingIncomingBlocks.
We call:
this.isSyncingIncomingBlocks = blocks;
replaceInnerBlocks( blocks );

When the replace operation is finished and we get new innerBlocks the reference of these inner blocks is not the same as the blocks we passed to replaceInnerBlocks. Because blocks are not saved as a tree in the state and the reference we get is new computation created by the getBlock selector.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the replace operation is finished and we get new innerBlocks the reference of these inner blocks is not the same as the blocks we passed to replaceInnerBlocks.

That's not ideal, could we get around it somehow? Maybe the reducer can keep a reference to the original array or something. In any case, this logic should not cause any issues if we leave it in until an enhancement is made to the store.

this.isSyncingIncomingBlocks = null;
} else if ( prevProps.block.innerBlocks !== innerBlocks ) {
this.isSyncingIncomingBlocks = null;

const resetFunc = isLastBlockChangePersistent ?
resetEditorBlocks :
resetEditorBlocksWithoutUndoLevel;
if ( resetFunc ) {
this.isSyncingOutcomingBlocks = innerBlocks;
resetFunc( innerBlocks );
}
}

// Accept changes to controlled blocks value from parent after a sync, if any.
if ( this.isSyncingOutcomingBlocks === blocks ) {
this.isSyncingOutcomingBlocks = null;
} else if ( prevProps.blocks !== blocks ) {
this.isSyncingOutcomingBlocks = null;

this.isSyncingIncomingBlocks = blocks;
replaceInnerBlocks( blocks );
}
}

/**
Expand Down Expand Up @@ -151,6 +190,7 @@ InnerBlocks = compose( [
getBlockListSettings,
getBlockRootClientId,
getTemplateLock,
isLastBlockChangePersistent,
} = select( 'core/block-editor' );
const { clientId } = ownProps;
const block = getBlock( clientId );
Expand All @@ -161,6 +201,7 @@ InnerBlocks = compose( [
blockListSettings: getBlockListSettings( clientId ),
hasOverlay: block.name !== 'core/template' && ! isBlockSelected( clientId ) && ! hasSelectedInnerBlock( clientId, true ),
parentLock: getTemplateLock( rootClientId ),
isLastBlockChangePersistent: isLastBlockChangePersistent(),
};
} ),
withDispatch( ( dispatch, ownProps ) => {
Expand Down
8 changes: 8 additions & 0 deletions packages/block-library/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ import * as tagCloud from './tag-cloud';

import * as classic from './classic';

// Custom Entity Blocks
import * as post from './post';
import * as postTitle from './post-title';

/**
* Function to register an individual block.
*
Expand Down Expand Up @@ -138,6 +142,10 @@ export const registerCoreBlocks = () => {
textColumns,
verse,
video,

// Register Custom Entity Blocks.
post,
postTitle,
].forEach( registerBlock );

setDefaultBlockName( paragraph.name );
Expand Down
4 changes: 4 additions & 0 deletions packages/block-library/src/post-title/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "core/post-title",
"category": "theme"
}
29 changes: 29 additions & 0 deletions packages/block-library/src/post-title/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { RichText } from '@wordpress/block-editor';
import { cleanForSlug } from '@wordpress/editor';
import { __ } from '@wordpress/i18n';

export default function PostTitleEdit() {
const title = useSelect(
( select ) => select( 'core/editor' ).getEditedEntityAttribute( 'title' ),
[]
);
const dispatch = useDispatch();
return (
<RichText
value={ title }
onChange={ ( value ) =>
dispatch( 'core/editor' ).editEntity( {
title: value,
slug: cleanForSlug( value ),
} )
}
tagName="h1"
placeholder={ __( 'Title' ) }
formattingControls={ [] }
/>
);
}
11 changes: 11 additions & 0 deletions packages/block-library/src/post-title/icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* WordPress dependencies
*/
import { SVG, Path } from '@wordpress/components';

export default (
<SVG xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24">
<Path fill="none" d="M0 0h24v24H0V0z" />
<Path d="M5 4v3h5.5v12h3V7H19V4H5z" />
</SVG>
);
20 changes: 20 additions & 0 deletions packages/block-library/src/post-title/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import metadata from './block.json';
import icon from './icon';
import edit from './edit';

const { name } = metadata;
export { metadata, name };

export const settings = {
title: __( 'Post Title' ),
icon,
edit,
};
Loading