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

Add ability to delete a Reusable Block to REST API #4137

Merged
merged 1 commit into from
Dec 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion blocks/api/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ export function createReusableBlock( type, attributes ) {
return {
id: +uniqueId(), // Temorary id replaced when the block is saved server side
isTemporary: true,
name: __( 'Untitled block' ),
title: __( 'Untitled block' ),
type,
attributes,
};
Expand Down
2 changes: 1 addition & 1 deletion blocks/api/test/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ describe( 'block factory', () => {

expect( createReusableBlock( type, attributes ) ).toMatchObject( {
id: expect.any( Number ),
name: 'Untitled block',
title: 'Untitled block',
type,
attributes,
} );
Expand Down
14 changes: 7 additions & 7 deletions blocks/library/block/edit-panel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import { __ } from '@wordpress/i18n';
import './style.scss';

function ReusableBlockEditPanel( props ) {
const { isEditing, name, isSaving, onEdit, onDetach, onChangeName, onSave, onCancel } = props;
const { isEditing, title, isSaving, onEdit, onDetach, onChangeTitle, onSave, onCancel } = props;

return (
<div className="reusable-block-edit-panel">
{ ! isEditing && ! isSaving && [
<span key="info" className="reusable-block-edit-panel__info">
<b>{ name }</b>
<b>{ title }</b>
</span>,
<Button
key="edit"
Expand All @@ -35,18 +35,18 @@ function ReusableBlockEditPanel( props ) {
] }
{ ( isEditing || isSaving ) && [
<input
key="name"
key="title"
type="text"
disabled={ isSaving }
className="reusable-block-edit-panel__name"
value={ name }
onChange={ ( event ) => onChangeName( event.target.value ) } />,
className="reusable-block-edit-panel__title"
value={ title }
onChange={ ( event ) => onChangeTitle( event.target.value ) } />,
<Button
key="save"
isPrimary
isLarge
isBusy={ isSaving }
disabled={ ! name || isSaving }
disabled={ ! title || isSaving }
className="reusable-block-edit-panel__button"
onClick={ onSave }>
{ __( 'Save' ) }
Expand Down
2 changes: 1 addition & 1 deletion blocks/library/block/edit-panel/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
margin-right: auto;
}

.reusable-block-edit-panel__name {
.reusable-block-edit-panel__title {
flex-grow: 1;
font-size: 14px;
height: 30px;
Expand Down
20 changes: 10 additions & 10 deletions blocks/library/block/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ class ReusableBlockEdit extends Component {
this.startEditing = this.startEditing.bind( this );
this.stopEditing = this.stopEditing.bind( this );
this.setAttributes = this.setAttributes.bind( this );
this.setName = this.setName.bind( this );
this.setTitle = this.setTitle.bind( this );
this.updateReusableBlock = this.updateReusableBlock.bind( this );

this.state = {
isEditing: false,
name: null,
title: null,
attributes: null,
};
}
Expand All @@ -48,7 +48,7 @@ class ReusableBlockEdit extends Component {
stopEditing() {
this.setState( {
isEditing: false,
name: null,
title: null,
attributes: null,
} );
}
Expand All @@ -59,16 +59,16 @@ class ReusableBlockEdit extends Component {
} ) );
}

setName( name ) {
this.setState( { name } );
setTitle( title ) {
this.setState( { title } );
}

updateReusableBlock() {
const { name, attributes } = this.state;
const { title, attributes } = this.state;

// Use pickBy to include only changed (assigned) values in payload
const payload = pickBy( {
name,
title,
attributes,
} );

Expand All @@ -79,7 +79,7 @@ class ReusableBlockEdit extends Component {

render() {
const { focus, reusableBlock, isSaving, convertBlockToStatic } = this.props;
const { isEditing, name, attributes } = this.state;
const { isEditing, title, attributes } = this.state;

if ( ! reusableBlock ) {
return <Placeholder><Spinner /></Placeholder>;
Expand All @@ -102,11 +102,11 @@ class ReusableBlockEdit extends Component {
<ReusableBlockEditPanel
key="panel"
isEditing={ isEditing }
name={ name !== null ? name : reusableBlock.name }
title={ title !== null ? title : reusableBlock.title }
isSaving={ isSaving }
onEdit={ this.startEditing }
onDetach={ convertBlockToStatic }
onChangeName={ this.setName }
onChangeTitle={ this.setTitle }
onSave={ this.updateReusableBlock }
onCancel={ this.stopEditing }
/>
Expand Down
2 changes: 1 addition & 1 deletion editor/components/inserter/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export class InserterMenu extends Component {
initialAttributes: {
ref: reusableBlock.id,
},
title: reusableBlock.name,
title: reusableBlock.title,
icon: 'layout',
category: 'reusable-blocks',
} ) );
Expand Down
14 changes: 7 additions & 7 deletions editor/store/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,18 +321,18 @@ export default {

let result;
if ( id ) {
result = new wp.api.models.ReusableBlocks( { id } ).fetch();
result = new wp.api.models.Blocks( { id } ).fetch();
} else {
result = new wp.api.collections.ReusableBlocks().fetch();
result = new wp.api.collections.Blocks().fetch();
}

result.then(
( reusableBlockOrBlocks ) => {
dispatch( {
type: 'FETCH_REUSABLE_BLOCKS_SUCCESS',
reusableBlocks: castArray( reusableBlockOrBlocks ).map( ( { id: itemId, name, content } ) => {
reusableBlocks: castArray( reusableBlockOrBlocks ).map( ( { id: itemId, title, content } ) => {
const [ { name: type, attributes } ] = parse( content );
return { id: itemId, name, type, attributes };
return { id: itemId, title, type, attributes };
} ),
} );
},
Expand All @@ -351,10 +351,10 @@ export default {
const { id } = action;
const { getState, dispatch } = store;

const { name, type, attributes, isTemporary } = getReusableBlock( getState(), id );
const { title, type, attributes, isTemporary } = getReusableBlock( getState(), id );
const content = serialize( createBlock( type, attributes ) );
const requestData = isTemporary ? { name, content } : { id, name, content };
new wp.api.models.ReusableBlocks( requestData ).save().then(
const requestData = isTemporary ? { title, content } : { id, title, content };
new wp.api.models.Blocks( requestData ).save().then(
( updatedReusableBlock ) => {
dispatch( {
type: 'SAVE_REUSABLE_BLOCK_SUCCESS',
Expand Down
24 changes: 12 additions & 12 deletions editor/store/test/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -501,12 +501,12 @@ describe( 'effects', () => {
const promise = Promise.resolve( [
{
id: 'a9691cf9-ecaa-42bd-a9ca-49587e817647',
name: 'My cool block',
title: 'My cool block',
content: '<!-- wp:core/test-block {"name":"Big Bird"} /-->',
},
] );

set( global, 'wp.api.collections.ReusableBlocks', class {
set( global, 'wp.api.collections.Blocks', class {
fetch() {
return promise;
}
Expand All @@ -523,7 +523,7 @@ describe( 'effects', () => {
reusableBlocks: [
{
id: 'a9691cf9-ecaa-42bd-a9ca-49587e817647',
name: 'My cool block',
title: 'My cool block',
type: 'core/test-block',
attributes: {
name: 'Big Bird',
Expand All @@ -540,11 +540,11 @@ describe( 'effects', () => {
let modelAttributes;
const promise = Promise.resolve( {
id,
name: 'My cool block',
title: 'My cool block',
content: '<!-- wp:core/test-block {"name":"Big Bird"} /-->',
} );

set( global, 'wp.api.models.ReusableBlocks', class {
set( global, 'wp.api.models.Blocks', class {
constructor( attributes ) {
modelAttributes = attributes;
}
Expand All @@ -566,7 +566,7 @@ describe( 'effects', () => {
reusableBlocks: [
{
id: 'a9691cf9-ecaa-42bd-a9ca-49587e817647',
name: 'My cool block',
title: 'My cool block',
type: 'core/test-block',
attributes: {
name: 'Big Bird',
Expand All @@ -580,7 +580,7 @@ describe( 'effects', () => {
it( 'should handle an API error', () => {
const promise = Promise.reject( {} );

set( global, 'wp.api.collections.ReusableBlocks', class {
set( global, 'wp.api.collections.Blocks', class {
fetch() {
return promise;
}
Expand Down Expand Up @@ -610,7 +610,7 @@ describe( 'effects', () => {
let modelAttributes;
const promise = Promise.resolve( { id: 3 } );

set( global, 'wp.api.models.ReusableBlocks', class {
set( global, 'wp.api.models.Blocks', class {
constructor( attributes ) {
modelAttributes = attributes;
}
Expand All @@ -622,7 +622,7 @@ describe( 'effects', () => {

const reusableBlock = {
id: '69f00b2b-ea09-4d5c-a98f-0b1112d7d400',
name: 'My cool block',
title: 'My cool block',
type: 'core/test-block',
attributes: {
name: 'Big Bird',
Expand All @@ -640,7 +640,7 @@ describe( 'effects', () => {

expect( modelAttributes ).toEqual( {
id: '69f00b2b-ea09-4d5c-a98f-0b1112d7d400',
name: 'My cool block',
title: 'My cool block',
content: '<!-- wp:test-block {\"name\":\"Big Bird\"} /-->',
} );
return promise.then( () => {
Expand All @@ -655,7 +655,7 @@ describe( 'effects', () => {
it( 'should handle an API error', () => {
const promise = Promise.reject( {} );

set( global, 'wp.api.models.ReusableBlocks', class {
set( global, 'wp.api.models.Blocks', class {
save() {
return promise;
}
Expand Down Expand Up @@ -753,7 +753,7 @@ describe( 'effects', () => {
updateReusableBlock( 1, {
id: 1,
isTemporary: true,
name: 'Untitled block',
title: 'Untitled block',
type: staticBlock.name,
attributes: staticBlock.attributes,
} )
Expand Down
116 changes: 116 additions & 0 deletions lib/class-wp-rest-blocks-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php
/**
* Reusable Blocks REST API: WP_REST_Blocks_Controller class
*
* @package gutenberg
* @since 0.10.0
*/

/**
* Controller which provides a REST endpoint for Gutenberg to read, create,
* edit and delete reusable blocks. Blocks are stored as posts with the
* wp_block post type.
*
* @since 0.10.0
*
* @see WP_REST_Controller
*/
class WP_REST_Blocks_Controller extends WP_REST_Posts_Controller {
/**
* Given an update or create request, build the post object that is saved to
* the database.
*
* @since 1.10.0
*
* @param WP_REST_Request $request Request object.
* @return stdClass|WP_Error Post object or WP_Error.
*/
protected function prepare_item_for_database( $request ) {
$prepared_post = new stdClass;

if ( isset( $request['id'] ) ) {
$existing_post = $this->get_post( $request['id'] );
if ( is_wp_error( $existing_post ) ) {
return $existing_post;
}

$prepared_post->ID = $existing_post->ID;
}

$prepared_post->post_title = $request['title'];
$prepared_post->post_content = $request['content'];
$prepared_post->post_type = $this->post_type;
$prepared_post->post_status = 'publish';

return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request );
}

/**
* Given a post from the database, build the array that is returned from an
* API response.
*
* @since 1.10.0
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $post, $request ) {
$data = array(
'id' => $post->ID,
'title' => $post->post_title,
'content' => $post->post_content,
);

$response = rest_ensure_response( $data );

return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request );
}

/**
* Handle a DELETE request.
*
* @since 1.10.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function delete_item( $request ) {
// Always hard-delete a block.
$request->set_param( 'force', true );

return parent::delete_item( $request );
}

/**
* Builds the block's schema, conforming to JSON Schema.
*
* @since 1.10.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
return array(
'$schema' => 'http://json-schema.org/schema#',
'title' => $this->post_type,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the block.', 'gutenberg' ),
'type' => 'integer',
'readonly' => true,
),
'title' => array(
'description' => __( 'The block\'s title.', 'gutenberg' ),
'type' => 'string',
'required' => true,
),
'content' => array(
'description' => __( 'The block\'s HTML content.', 'gutenberg' ),
'type' => 'string',
'required' => true,
),
),
);
}
}
Loading