Skip to content

Commit edaf84f

Browse files
aduthjorgefilipecosta
authored andcommitted
Block Library: Reimplement Reusable Block using EditorProvider for embedded post editor
1 parent cae7bb7 commit edaf84f

File tree

5 files changed

+224
-139
lines changed

5 files changed

+224
-139
lines changed

packages/block-library/src/block/edit-panel/index.js

+35-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { Button } from '@wordpress/components';
55
import { Component, createRef } from '@wordpress/element';
66
import { __ } from '@wordpress/i18n';
77
import { ESCAPE } from '@wordpress/keycodes';
8-
import { withInstanceId } from '@wordpress/compose';
8+
import { withSelect, withDispatch } from '@wordpress/data';
9+
import { withInstanceId, compose } from '@wordpress/compose';
910

1011
class ReusableBlockEditPanel extends Component {
1112
constructor() {
@@ -107,4 +108,36 @@ class ReusableBlockEditPanel extends Component {
107108
}
108109
}
109110

110-
export default withInstanceId( ReusableBlockEditPanel );
111+
export default compose( [
112+
withInstanceId,
113+
withSelect( ( select ) => {
114+
const { getEditedPostAttribute, isSavingPost } = select( 'core/editor' );
115+
116+
return {
117+
title: getEditedPostAttribute( 'title' ),
118+
isSaving: isSavingPost(),
119+
};
120+
} ),
121+
withDispatch( ( dispatch, ownProps ) => {
122+
const {
123+
editPost,
124+
savePost,
125+
clearSelectedBlock,
126+
} = dispatch( 'core/editor' );
127+
128+
return {
129+
onChangeTitle( title ) {
130+
editPost( { title } );
131+
},
132+
onSave() {
133+
clearSelectedBlock();
134+
savePost();
135+
ownProps.onSave();
136+
},
137+
onCancel() {
138+
clearSelectedBlock();
139+
ownProps.onCancel();
140+
},
141+
};
142+
} ),
143+
] )( ReusableBlockEditPanel );
+125-137
Original file line numberDiff line numberDiff line change
@@ -1,187 +1,175 @@
1-
/**
2-
* External dependencies
3-
*/
4-
import { noop, partial } from 'lodash';
5-
61
/**
72
* WordPress dependencies
83
*/
94
import { Component } from '@wordpress/element';
10-
import { Placeholder, Spinner, Disabled } from '@wordpress/components';
11-
import { withSelect, withDispatch } from '@wordpress/data';
5+
import { Placeholder, Spinner, Disabled, SlotFillProvider } from '@wordpress/components';
6+
import {
7+
withSelect,
8+
withDispatch,
9+
withRegistry,
10+
} from '@wordpress/data';
1211
import { __ } from '@wordpress/i18n';
13-
import { BlockEdit } from '@wordpress/block-editor';
12+
import {
13+
BlockList,
14+
BlockControls,
15+
BlockFormatControls,
16+
FormatToolbar,
17+
__experimentalBlockSettingsMenuFirstItem,
18+
__experimentalBlockSettingsMenuPluginsExtension,
19+
} from '@wordpress/block-editor';
20+
import { EditorProvider } from '@wordpress/editor';
1421
import { compose } from '@wordpress/compose';
1522

1623
/**
1724
* Internal dependencies
1825
*/
1926
import ReusableBlockEditPanel from './edit-panel';
2027
import ReusableBlockIndicator from './indicator';
28+
import SelectionObserver from './selection-observer';
2129

2230
class ReusableBlockEdit extends Component {
23-
constructor( { reusableBlock } ) {
31+
constructor() {
2432
super( ...arguments );
2533

26-
this.startEditing = this.startEditing.bind( this );
27-
this.stopEditing = this.stopEditing.bind( this );
28-
this.setAttributes = this.setAttributes.bind( this );
29-
this.setTitle = this.setTitle.bind( this );
30-
this.save = this.save.bind( this );
31-
32-
if ( reusableBlock && reusableBlock.isTemporary ) {
33-
// Start in edit mode when we're working with a newly created reusable block
34-
this.state = {
35-
isEditing: true,
36-
title: reusableBlock.title,
37-
changedAttributes: {},
38-
};
39-
} else {
40-
// Start in preview mode when we're working with an existing reusable block
41-
this.state = {
42-
isEditing: false,
43-
title: null,
44-
changedAttributes: null,
45-
};
46-
}
47-
}
48-
49-
componentDidMount() {
50-
if ( ! this.props.reusableBlock ) {
51-
this.props.fetchReusableBlock();
52-
}
53-
}
54-
55-
startEditing() {
56-
const { reusableBlock } = this.props;
57-
58-
this.setState( {
59-
isEditing: true,
60-
title: reusableBlock.title,
61-
changedAttributes: {},
62-
} );
63-
}
34+
this.startEditing = () => this.toggleIsEditing( true );
35+
this.stopEditing = () => this.toggleIsEditing( false );
36+
this.cancelEditing = this.cancelEditing.bind( this );
6437

65-
stopEditing() {
66-
this.setState( {
38+
this.state = {
39+
cancelIncrementKey: 0,
40+
// TODO: Check if this needs to consider reusable block being temporary (this was in original PR)
6741
isEditing: false,
68-
title: null,
69-
changedAttributes: null,
70-
} );
71-
}
72-
73-
setAttributes( attributes ) {
74-
this.setState( ( prevState ) => {
75-
if ( prevState.changedAttributes !== null ) {
76-
return { changedAttributes: { ...prevState.changedAttributes, ...attributes } };
77-
}
78-
} );
42+
};
7943
}
8044

81-
setTitle( title ) {
82-
this.setState( { title } );
45+
/**
46+
* Starts or stops editing, corresponding to the given boolean value.
47+
*
48+
* @param {boolean} isEditing Whether editing mode should be made active.
49+
*/
50+
toggleIsEditing( isEditing ) {
51+
this.setState( { isEditing } );
8352
}
8453

85-
save() {
86-
const { reusableBlock, onUpdateTitle, updateAttributes, block, onSave } = this.props;
87-
const { title, changedAttributes } = this.state;
88-
89-
if ( title !== reusableBlock.title ) {
90-
onUpdateTitle( title );
91-
}
92-
93-
updateAttributes( block.clientId, changedAttributes );
94-
onSave();
95-
54+
/**
55+
* Stops editing and restores the reusable block to its original saved
56+
* state.
57+
*/
58+
cancelEditing() {
9659
this.stopEditing();
60+
61+
// Cancelling takes effect by assigning a new key for the rendered
62+
// EditorProvider which forces a re-mount to reset editing state.
63+
let { cancelIncrementKey } = this.state;
64+
cancelIncrementKey++;
65+
this.setState( { cancelIncrementKey } );
9766
}
9867

9968
render() {
100-
const { isSelected, reusableBlock, block, isFetching, isSaving, canUpdateBlock } = this.props;
101-
const { isEditing, title, changedAttributes } = this.state;
102-
103-
if ( ! reusableBlock && isFetching ) {
104-
return <Placeholder><Spinner /></Placeholder>;
105-
}
106-
107-
if ( ! reusableBlock || ! block ) {
108-
return <Placeholder>{ __( 'Block has been deleted or is unavailable.' ) }</Placeholder>;
69+
const {
70+
isSelected,
71+
reusableBlock,
72+
isFetching,
73+
canUpdateBlock,
74+
settings,
75+
} = this.props;
76+
const { cancelIncrementKey, isEditing } = this.state;
77+
78+
if ( ! reusableBlock ) {
79+
return (
80+
<Placeholder>
81+
{
82+
isFetching ?
83+
<Spinner /> :
84+
__( 'Block has been deleted or is unavailable.' )
85+
}
86+
</Placeholder>
87+
);
10988
}
11089

111-
let element = (
112-
<BlockEdit
113-
{ ...this.props }
114-
isSelected={ isEditing && isSelected }
115-
clientId={ block.clientId }
116-
name={ block.name }
117-
attributes={ { ...block.attributes, ...changedAttributes } }
118-
setAttributes={ isEditing ? this.setAttributes : noop }
119-
/>
120-
);
121-
90+
let list = <BlockList />;
12291
if ( ! isEditing ) {
123-
element = <Disabled>{ element }</Disabled>;
92+
list = <Disabled>{ list }</Disabled>;
12493
}
12594

12695
return (
127-
<>
128-
{ ( isSelected || isEditing ) && (
129-
<ReusableBlockEditPanel
130-
isEditing={ isEditing }
131-
title={ title !== null ? title : reusableBlock.title }
132-
isSaving={ isSaving && ! reusableBlock.isTemporary }
133-
isEditDisabled={ ! canUpdateBlock }
134-
onEdit={ this.startEditing }
135-
onChangeTitle={ this.setTitle }
136-
onSave={ this.save }
137-
onCancel={ this.stopEditing }
96+
<SlotFillProvider
97+
slots={ [
98+
FormatToolbar.Slot,
99+
BlockControls.Slot,
100+
BlockFormatControls.Slot,
101+
__experimentalBlockSettingsMenuFirstItem.Slot,
102+
__experimentalBlockSettingsMenuPluginsExtension.Slot,
103+
] }
104+
>
105+
<EditorProvider
106+
key={ cancelIncrementKey }
107+
post={ reusableBlock }
108+
settings={ { ...settings, templateLock: ! isEditing } }
109+
>
110+
<SelectionObserver
111+
isParentSelected={ isSelected }
112+
onBlockSelected={ this.props.selectBlock }
138113
/>
139-
) }
140-
{ ! isSelected && ! isEditing && <ReusableBlockIndicator title={ reusableBlock.title } /> }
141-
{ element }
142-
</>
114+
{ ( isSelected || isEditing ) && (
115+
<ReusableBlockEditPanel
116+
isEditing={ isEditing }
117+
isEditDisabled={ ! canUpdateBlock }
118+
onEdit={ this.startEditing }
119+
onSave={ this.stopEditing }
120+
onCancel={ this.cancelEditing }
121+
/>
122+
) }
123+
{ ! isSelected && ! isEditing && (
124+
<ReusableBlockIndicator title={ reusableBlock.title } />
125+
) }
126+
{ list }
127+
</EditorProvider>
128+
</SlotFillProvider>
143129
);
144130
}
145131
}
146132

147133
export default compose( [
134+
withRegistry,
148135
withSelect( ( select, ownProps ) => {
149-
const {
150-
__experimentalGetReusableBlock: getReusableBlock,
151-
__experimentalIsFetchingReusableBlock: isFetchingReusableBlock,
152-
__experimentalIsSavingReusableBlock: isSavingReusableBlock,
153-
} = select( 'core/editor' );
154-
const { canUser } = select( 'core' );
155-
const {
156-
getBlock,
157-
} = select( 'core/block-editor' );
158-
const { ref } = ownProps.attributes;
159-
const reusableBlock = getReusableBlock( ref );
136+
const { clientId, attributes } = ownProps;
137+
const { ref } = attributes;
138+
const { canUser, getEntityRecord } = select( 'core' );
139+
const { isResolving } = select( 'core/data' );
140+
const { getEditorSettings } = select( 'core/editor' );
141+
const { isBlockSelected } = select( 'core/block-editor' );
142+
143+
const isTemporaryReusableBlock = ! Number.isFinite( ref );
144+
145+
let reusableBlock;
146+
if ( ! isTemporaryReusableBlock ) {
147+
reusableBlock = getEntityRecord( 'postType', 'wp_block', ref );
148+
}
160149

161150
return {
162151
reusableBlock,
163-
isFetching: isFetchingReusableBlock( ref ),
164-
isSaving: isSavingReusableBlock( ref ),
165-
block: reusableBlock ? getBlock( reusableBlock.clientId ) : null,
166-
canUpdateBlock: !! reusableBlock && ! reusableBlock.isTemporary && !! canUser( 'update', 'blocks', ref ),
152+
isSelected: isBlockSelected( clientId ),
153+
isFetching: isResolving(
154+
'core',
155+
'getEntityRecord',
156+
[ 'postType', 'wp_block', ref ]
157+
),
158+
canUpdateBlock: (
159+
!! reusableBlock &&
160+
! isTemporaryReusableBlock &&
161+
!! canUser( 'update', 'blocks', ref )
162+
),
163+
settings: getEditorSettings(),
167164
};
168165
} ),
169166
withDispatch( ( dispatch, ownProps ) => {
170-
const {
171-
__experimentalFetchReusableBlocks: fetchReusableBlocks,
172-
__experimentalUpdateReusableBlockTitle: updateReusableBlockTitle,
173-
__experimentalSaveReusableBlock: saveReusableBlock,
174-
} = dispatch( 'core/editor' );
175-
const {
176-
updateBlockAttributes,
177-
} = dispatch( 'core/block-editor' );
178-
const { ref } = ownProps.attributes;
167+
const { selectBlock } = dispatch( 'core/block-editor' );
179168

180169
return {
181-
fetchReusableBlock: partial( fetchReusableBlocks, ref ),
182-
updateAttributes: updateBlockAttributes,
183-
onUpdateTitle: partial( updateReusableBlockTitle, ref ),
184-
onSave: partial( saveReusableBlock, ref ),
170+
selectBlock() {
171+
selectBlock( ownProps.clientId );
172+
},
185173
};
186174
} ),
187175
] )( ReusableBlockEdit );
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.block-editor-block-list__block[data-type="core/block"] {
2+
.block-editor-block-list__layout > .block-editor-block-list__block:first-child > .block-editor-block-list__block-edit {
3+
margin-top: 0;
4+
}
5+
6+
.block-editor-block-list__layout > .block-editor-block-list__block:last-child > .block-editor-block-list__block-edit {
7+
margin-bottom: 0;
8+
}
9+
}

0 commit comments

Comments
 (0)