diff --git a/packages/block-library/src/block/edit-panel/index.js b/packages/block-library/src/block/edit-panel/index.js
index eb620a877b63a1..a88bb8b3bddb6b 100644
--- a/packages/block-library/src/block/edit-panel/index.js
+++ b/packages/block-library/src/block/edit-panel/index.js
@@ -5,7 +5,8 @@ import { Button } from '@wordpress/components';
import { Component, Fragment, createRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { ESCAPE } from '@wordpress/keycodes';
-import { withInstanceId } from '@wordpress/compose';
+import { withSelect, withDispatch } from '@wordpress/data';
+import { withInstanceId, compose } from '@wordpress/compose';
class ReusableBlockEditPanel extends Component {
constructor() {
@@ -107,4 +108,36 @@ class ReusableBlockEditPanel extends Component {
}
}
-export default withInstanceId( ReusableBlockEditPanel );
+export default compose( [
+ withInstanceId,
+ withSelect( ( select ) => {
+ const { getEditedPostAttribute, isSavingPost } = select( 'core/editor' );
+
+ return {
+ title: getEditedPostAttribute( 'title' ),
+ isSaving: isSavingPost(),
+ };
+ } ),
+ withDispatch( ( dispatch, ownProps ) => {
+ const {
+ editPost,
+ savePost,
+ clearSelectedBlock,
+ } = dispatch( 'core/editor' );
+
+ return {
+ onChangeTitle( title ) {
+ editPost( { title } );
+ },
+ onSave() {
+ clearSelectedBlock();
+ savePost();
+ ownProps.onSave();
+ },
+ onCancel() {
+ clearSelectedBlock();
+ ownProps.onCancel();
+ },
+ };
+ } ),
+] )( ReusableBlockEditPanel );
diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js
index 722aad97c9bfcc..8146170021adf4 100644
--- a/packages/block-library/src/block/edit.js
+++ b/packages/block-library/src/block/edit.js
@@ -1,16 +1,23 @@
-/**
- * External dependencies
- */
-import { noop, partial } from 'lodash';
-
/**
* WordPress dependencies
*/
-import { Component, Fragment } from '@wordpress/element';
-import { Placeholder, Spinner, Disabled } from '@wordpress/components';
-import { withSelect, withDispatch } from '@wordpress/data';
+import { Component } from '@wordpress/element';
+import { Placeholder, Spinner, Disabled, SlotFillProvider } from '@wordpress/components';
+import {
+ withSelect,
+ withDispatch,
+ withRegistry,
+} from '@wordpress/data';
import { __ } from '@wordpress/i18n';
-import { BlockEdit } from '@wordpress/block-editor';
+import {
+ BlockList,
+ BlockControls,
+ BlockFormatControls,
+ FormatToolbar,
+ __experimentalBlockSettingsMenuFirstItem,
+ __experimentalBlockSettingsMenuPluginsExtension,
+} from '@wordpress/block-editor';
+import { EditorProvider } from '@wordpress/editor';
import { compose } from '@wordpress/compose';
/**
@@ -18,170 +25,151 @@ import { compose } from '@wordpress/compose';
*/
import ReusableBlockEditPanel from './edit-panel';
import ReusableBlockIndicator from './indicator';
+import SelectionObserver from './selection-observer';
class ReusableBlockEdit extends Component {
- constructor( { reusableBlock } ) {
+ constructor() {
super( ...arguments );
- this.startEditing = this.startEditing.bind( this );
- this.stopEditing = this.stopEditing.bind( this );
- this.setAttributes = this.setAttributes.bind( this );
- this.setTitle = this.setTitle.bind( this );
- this.save = this.save.bind( this );
-
- if ( reusableBlock && reusableBlock.isTemporary ) {
- // Start in edit mode when we're working with a newly created reusable block
- this.state = {
- isEditing: true,
- title: reusableBlock.title,
- changedAttributes: {},
- };
- } else {
- // Start in preview mode when we're working with an existing reusable block
- this.state = {
- isEditing: false,
- title: null,
- changedAttributes: null,
- };
- }
- }
-
- componentDidMount() {
- if ( ! this.props.reusableBlock ) {
- this.props.fetchReusableBlock();
- }
- }
-
- startEditing() {
- const { reusableBlock } = this.props;
-
- this.setState( {
- isEditing: true,
- title: reusableBlock.title,
- changedAttributes: {},
- } );
- }
+ this.startEditing = () => this.toggleIsEditing( true );
+ this.stopEditing = () => this.toggleIsEditing( false );
+ this.cancelEditing = this.cancelEditing.bind( this );
- stopEditing() {
- this.setState( {
+ this.state = {
+ cancelIncrementKey: 0,
+ // TODO: Check if this needs to consider reusable block being temporary (this was in original PR)
isEditing: false,
- title: null,
- changedAttributes: null,
- } );
- }
-
- setAttributes( attributes ) {
- this.setState( ( prevState ) => {
- if ( prevState.changedAttributes !== null ) {
- return { changedAttributes: { ...prevState.changedAttributes, ...attributes } };
- }
- } );
+ };
}
- setTitle( title ) {
- this.setState( { title } );
+ /**
+ * Starts or stops editing, corresponding to the given boolean value.
+ *
+ * @param {boolean} isEditing Whether editing mode should be made active.
+ */
+ toggleIsEditing( isEditing ) {
+ this.setState( { isEditing } );
}
- save() {
- const { reusableBlock, onUpdateTitle, updateAttributes, block, onSave } = this.props;
- const { title, changedAttributes } = this.state;
-
- if ( title !== reusableBlock.title ) {
- onUpdateTitle( title );
- }
-
- updateAttributes( block.clientId, changedAttributes );
- onSave();
-
+ /**
+ * Stops editing and restores the reusable block to its original saved
+ * state.
+ */
+ cancelEditing() {
this.stopEditing();
+
+ // Cancelling takes effect by assigning a new key for the rendered
+ // EditorProvider which forces a re-mount to reset editing state.
+ let { cancelIncrementKey } = this.state;
+ cancelIncrementKey++;
+ this.setState( { cancelIncrementKey } );
}
render() {
- const { isSelected, reusableBlock, block, isFetching, isSaving, canUpdateBlock } = this.props;
- const { isEditing, title, changedAttributes } = this.state;
-
- if ( ! reusableBlock && isFetching ) {
- return ;
- }
-
- if ( ! reusableBlock || ! block ) {
- return { __( 'Block has been deleted or is unavailable.' ) };
+ const {
+ isSelected,
+ reusableBlock,
+ isFetching,
+ canUpdateBlock,
+ settings,
+ } = this.props;
+ const { cancelIncrementKey, isEditing } = this.state;
+
+ if ( ! reusableBlock ) {
+ return (
+
+ {
+ isFetching ?
+ :
+ __( 'Block has been deleted or is unavailable.' )
+ }
+
+ );
}
- let element = (
-
- );
-
+ let list = ;
if ( ! isEditing ) {
- element = { element };
+ list = { list };
}
return (
-
- { ( isSelected || isEditing ) && (
-
+
+
- ) }
- { ! isSelected && ! isEditing && }
- { element }
-
+ { ( isSelected || isEditing ) && (
+
+ ) }
+ { ! isSelected && ! isEditing && (
+
+ ) }
+ { list }
+
+
);
}
}
export default compose( [
+ withRegistry,
withSelect( ( select, ownProps ) => {
- const {
- __experimentalGetReusableBlock: getReusableBlock,
- __experimentalIsFetchingReusableBlock: isFetchingReusableBlock,
- __experimentalIsSavingReusableBlock: isSavingReusableBlock,
- } = select( 'core/editor' );
- const { canUser } = select( 'core' );
- const {
- getBlock,
- } = select( 'core/block-editor' );
- const { ref } = ownProps.attributes;
- const reusableBlock = getReusableBlock( ref );
+ const { clientId, attributes } = ownProps;
+ const { ref } = attributes;
+ const { canUser, getEntityRecord } = select( 'core' );
+ const { isResolving } = select( 'core/data' );
+ const { getEditorSettings } = select( 'core/editor' );
+ const { isBlockSelected } = select( 'core/block-editor' );
+
+ const isTemporaryReusableBlock = ! Number.isFinite( ref );
+
+ let reusableBlock;
+ if ( ! isTemporaryReusableBlock ) {
+ reusableBlock = getEntityRecord( 'postType', 'wp_block', ref );
+ }
return {
reusableBlock,
- isFetching: isFetchingReusableBlock( ref ),
- isSaving: isSavingReusableBlock( ref ),
- block: reusableBlock ? getBlock( reusableBlock.clientId ) : null,
- canUpdateBlock: !! reusableBlock && ! reusableBlock.isTemporary && !! canUser( 'update', 'blocks', ref ),
+ isSelected: isBlockSelected( clientId ),
+ isFetching: isResolving(
+ 'core',
+ 'getEntityRecord',
+ [ 'postType', 'wp_block', ref ]
+ ),
+ canUpdateBlock: (
+ !! reusableBlock &&
+ ! isTemporaryReusableBlock &&
+ !! canUser( 'update', 'blocks', ref )
+ ),
+ settings: getEditorSettings(),
};
} ),
withDispatch( ( dispatch, ownProps ) => {
- const {
- __experimentalFetchReusableBlocks: fetchReusableBlocks,
- __experimentalUpdateReusableBlockTitle: updateReusableBlockTitle,
- __experimentalSaveReusableBlock: saveReusableBlock,
- } = dispatch( 'core/editor' );
- const {
- updateBlockAttributes,
- } = dispatch( 'core/block-editor' );
- const { ref } = ownProps.attributes;
+ const { selectBlock } = dispatch( 'core/block-editor' );
return {
- fetchReusableBlock: partial( fetchReusableBlocks, ref ),
- updateAttributes: updateBlockAttributes,
- onUpdateTitle: partial( updateReusableBlockTitle, ref ),
- onSave: partial( saveReusableBlock, ref ),
+ selectBlock() {
+ selectBlock( ownProps.clientId );
+ },
};
} ),
] )( ReusableBlockEdit );
diff --git a/packages/block-library/src/block/editor.scss b/packages/block-library/src/block/editor.scss
new file mode 100644
index 00000000000000..7523878adf2d86
--- /dev/null
+++ b/packages/block-library/src/block/editor.scss
@@ -0,0 +1,9 @@
+.block-editor-block-list__block[data-type="core/block"] {
+ .block-editor-block-list__layout > .block-editor-block-list__block:first-child > .block-editor-block-list__block-edit {
+ margin-top: 0;
+ }
+
+ .block-editor-block-list__layout > .block-editor-block-list__block:last-child > .block-editor-block-list__block-edit {
+ margin-bottom: 0;
+ }
+}
diff --git a/packages/block-library/src/block/selection-observer/index.js b/packages/block-library/src/block/selection-observer/index.js
new file mode 100644
index 00000000000000..e95bc80e525cbe
--- /dev/null
+++ b/packages/block-library/src/block/selection-observer/index.js
@@ -0,0 +1,54 @@
+/**
+ * WordPress dependencies
+ */
+import { Component } from '@wordpress/element';
+import { compose } from '@wordpress/compose';
+import { withSelect, withDispatch } from '@wordpress/data';
+
+/**
+ * Component which calls onBlockSelected prop when a block becomes selected. It
+ * is assumed to be used in a separate registry context from the reusable block
+ * in which it is rendered, ensuring that only one block appears as selected
+ * between the editor in which the reusable resides and block's own editor.
+ *
+ * @type {WPComponent}
+ */
+class SelectionObserver extends Component {
+ componentDidUpdate( prevProps ) {
+ const {
+ hasSelectedBlock,
+ onBlockSelected,
+ isParentSelected,
+ clearSelectedBlock,
+ } = this.props;
+
+ if ( hasSelectedBlock && ! prevProps.hasSelectedBlock ) {
+ onBlockSelected();
+ }
+
+ if ( ! isParentSelected && prevProps.isParentSelected ) {
+ clearSelectedBlock();
+ }
+ }
+
+ render() {
+ return null;
+ }
+}
+
+export default compose( [
+ withSelect( ( select ) => {
+ const { hasSelectedBlock } = select( 'core/block-editor' );
+
+ return {
+ hasSelectedBlock: hasSelectedBlock(),
+ };
+ } ),
+ withDispatch( ( dispatch ) => {
+ const { clearSelectedBlock } = dispatch( 'core/block-editor' );
+
+ return {
+ clearSelectedBlock,
+ };
+ } ),
+] )( SelectionObserver );
diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss
index 5599dafd7c463f..419c42f9f2d9f2 100644
--- a/packages/block-library/src/style.scss
+++ b/packages/block-library/src/style.scss
@@ -1,4 +1,5 @@
@import "./audio/style.scss";
+@import "./block/editor.scss";
@import "./block/edit-panel/style.scss";
@import "./block/indicator/style.scss";
@import "./button/style.scss";