Skip to content

Commit

Permalink
Introduce custom block patterns saved as a CPT. (godaddy-wordpress#1659)
Browse files Browse the repository at this point in the history
* Create new layotu CPT and taxonomies

* Load the layouts from the CPT into the layout selector

* Remove Gutenberg requirement for layout selector

* Update dependencies

* Add new block patterns modal

* Make it easier to link existing/non-existing terms to a pattern

* Register block_pattern_category tax with CPT

* Add excerpt support

* Load block patterns

* prefix CPT and taxonomies with "coblocks"

* typo

* Mistakenly removed gutter-control require

* Mistakenly removed gutter-control require

* Manually trigger click even on "Block Patterns" tab

* Remove icon from block settings menu item

* Minor copy updates

* Update CPT prefix to coblocks_pattern

* Update CPT prefix

* Remove whitespace

* Update index.js

* Add required * to Name

* Adding new singleton trait

* Use singleton trait

* Adding inline docs

* Use constants for the slugs

* Fix const usage

* Add method visibility and missing inline docs

Co-authored-by: Rich Tabor <[email protected]>
  • Loading branch information
jrtashjian and richtabor authored Sep 3, 2020
1 parent 50613ff commit e65e9f8
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 3 deletions.
66 changes: 63 additions & 3 deletions includes/class-block-patterns.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public function __construct() {
add_action( 'init', array( $this, 'register_post_type' ) );
add_action( 'init', array( $this, 'register_type_taxonomy' ) );
add_action( 'init', array( $this, 'register_category_taxonomy' ) );
add_action( 'init', array( $this, 'load_block_patterns' ) );
add_action( 'rest_insert_block_patterns', array( $this, 'add_taxonomies_on_insert_post' ), 10, 2 );

add_filter( 'coblocks_layout_selector_categories', array( $this, 'load_categories' ) );
add_filter( 'coblocks_layout_selector_layouts', array( $this, 'load_layouts' ) );
Expand All @@ -38,7 +40,7 @@ public function register_post_type() {
$args = array(
'label' => __( 'Block Patterns', 'coblocks' ),
'description' => __( 'Description', 'coblocks' ),
'supports' => array( 'title', 'editor' ),
'supports' => array( 'title', 'editor', 'excerpt' ),
'taxonomies' => array(
self::TYPE_TAXONOMY,
self::CATEGORY_TAXONOMY,
Expand Down Expand Up @@ -67,7 +69,7 @@ public function register_type_taxonomy() {
'show_in_rest' => true,
);

register_taxonomy( self::TYPE_TAXONOMY, array( 'coblocks_pattern' ), $args );
register_taxonomy( self::TYPE_TAXONOMY, array( self::POST_TYPE ), $args );
}

/**
Expand All @@ -83,7 +85,23 @@ public function register_category_taxonomy() {
'show_in_rest' => true,
);

register_taxonomy( self::CATEGORY_TAXONOMY, array( 'coblocks_pattern' ), $args );
register_taxonomy( self::CATEGORY_TAXONOMY, array( self::POST_TYPE ), $args );
}

/**
* Set custom taxonomies relationships with the REST API.
*
* @param WP_Post $post Inserted or updated post object.
* @param WP_REST_Request $request Request object.
*/
public function add_taxonomies_on_insert_post( $post, $request ) {
$params = $request->get_json_params();

if ( array_key_exists( 'terms', $params ) ) {
foreach ( $params['terms'] as $taxonomy => $terms ) {
wp_set_object_terms( $post->ID, $terms, $taxonomy );
}
}
}

/**
Expand Down Expand Up @@ -154,6 +172,48 @@ public function load_layouts( $layouts ) {

return $layouts;
}

/**
* Register custom post type posts (with the 'pattern' type) as block patterns.
*/
public function load_block_patterns() {
$query_args = array(
'post_type' => self::POST_TYPE,

'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,

'tax_query' => array(
array(
'taxonomy' => self::TYPE_TAXONOMY,
'field' => 'slug',
'terms' => 'pattern',
),
),
);

$block_patterns_query = new \WP_Query( $query_args );
wp_reset_postdata();

if ( empty( $block_patterns_query->posts ) ) {
return;
}

foreach ( $block_patterns_query->posts as $block_pattern ) {
$categories = get_the_terms( $block_pattern->ID, self::CATEGORY_TAXONOMY );

register_block_pattern(
self::POST_TYPE . '/' . $block_pattern->post_name,
array(
'title' => $block_pattern->post_title,
'content' => $block_pattern->post_content,
'categories' => empty( $categories ) ? array() : wp_list_pluck( $categories, 'slug' ),
'description' => $block_pattern->post_excerpt,
)
);
}
}
}

CoBlocks_Block_Patterns::register();
1 change: 1 addition & 0 deletions src/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import './extensions/replace-image';
import './extensions/image-crop';
import './extensions/coblocks-settings/';
import './extensions/layout-selector';
import './extensions/block-patterns';

// Internal Extensions / Components
import './components/gutter-control';
Expand Down
45 changes: 45 additions & 0 deletions src/extensions/block-patterns/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { registerPlugin } from '@wordpress/plugins';
import { Fragment, useState } from '@wordpress/element';
import { BlockSettingsMenuControls } from '@wordpress/block-editor';
import { MenuItem } from '@wordpress/components';

/**
* Internal dependencies
*/
import CoBlocksBlockPatternsModal from './modal';
import './styles/style.scss';

const CoBlocksBlockPatterns = () => {
const [ isOpen, setOpen ] = useState( false );

const openModal = () => setOpen( true );
const closeModal = () => setOpen( false );

const props = { isOpen, openModal, closeModal };

return (
<Fragment>
<BlockSettingsMenuControls>
{ ( { onClose } ) => (
<MenuItem
onClick={ () => {
openModal();
onClose();
} }
>
{ __( 'Add Design Pattern', 'coblocks' ) }
</MenuItem>
) }
</BlockSettingsMenuControls>
<CoBlocksBlockPatternsModal { ...props } />
</Fragment>
);
};

registerPlugin( 'coblocks-block-patterns', {
render: CoBlocksBlockPatterns,
} );
192 changes: 192 additions & 0 deletions src/extensions/block-patterns/modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { withSelect, withDispatch } from '@wordpress/data';
import { BlockPreview } from '@wordpress/block-editor';
import { serialize } from '@wordpress/blocks';
import apiFetch from '@wordpress/api-fetch';
import {
Button,
Modal,
TextControl,
SelectControl,
} from '@wordpress/components';

class CoBlocksBlockPatternsModal extends Component {
constructor( props ) {
super( props );

this.state = {
name: '',
description: '',
category: '',
};
}

savePattern = ( event ) => {
event.preventDefault();

const {
category,
description,
name,
} = this.state;

const {
closeModal,
createErrorNotice,
createSuccessNotice,
selectedBlocks,
setIsInserterOpened,
} = this.props;

apiFetch( {
path: '/wp/v2/coblocks_pattern',
method: 'POST',
data: {
title: name,
slug: name,
content: serialize( selectedBlocks ),
excerpt: description,
status: 'publish',
terms: {
coblocks_pattern_type: [ 'pattern' ],
coblocks_pattern_category: [ category ],
},
},
} )
.then( () => {
closeModal();

// Inject block pattern into the inserter.
this.props.updateSettings( {
...this.props.getSettings(),
__experimentalBlockPatterns: [
...this.props.getSettings().__experimentalBlockPatterns,
{
title: name,
name: `coblocks_pattern/${ name }`,
content: serialize( selectedBlocks ),
description,
categories: [ category ],
},
],
} );

createSuccessNotice(
sprintf(
// translators: %s is the pattern name.
__( '"%s" pattern has been saved.', 'coblocks' ),
name
),
{
type: 'snackbar',
actions: [
{
label: __( 'View Patterns', 'coblocks' ),
onClick: () => {
setIsInserterOpened( true );
setTimeout( () => document.getElementById( 'tab-panel-1-patterns' ).click(), 1000 );
},
},
],
}
);
} )
.catch( () => {
createErrorNotice( __( 'Failed to save new pattern.', 'coblocks' ) );
} );
}

render() {
const {
blockPatternCategories,
closeModal,
isOpen,
selectedBlocks,
} = this.props;

return isOpen && (
<Modal
title={ __( 'Add Design Pattern', 'coblocks' ) }
onRequestClose={ closeModal }
className="coblocks-block-patterns__modal"
>
<div className="coblocks-block-patterns__preview">
<BlockPreview
autoHeight
blocks={ selectedBlocks }
/>
</div>
<form onSubmit={ this.savePattern }>
<TextControl
label={ __( 'Name', 'coblocks' ) + '*' }
onChange={ ( name ) => this.setState( { name } ) }
required
/>
<TextControl
label={ __( 'Description', 'coblocks' ) }
onChange={ ( description ) => this.setState( { description } ) }
/>
<SelectControl
label={ __( 'Category', 'coblocks' ) }
options={ [
{ label: __( 'Select a pattern category', 'coblocks' ), value: '' },
...blockPatternCategories.map( ( category ) => ( { ...category, value: category.name } ) ),
] }
onChange={ ( category ) => this.setState( { category } ) }
/>
<Button isPrimary type="submit">
{ __( 'Add Pattern', 'coblocks' ) }
</Button>
</form>
</Modal>
);
}
}

export default compose( [

withSelect( ( select ) => {
const {
getMultiSelectedBlocks,
getSelectedBlock,
getSelectedBlockCount,
getSettings,
} = select( 'core/block-editor' );

return {
getSettings,
selectedBlocks: getSelectedBlockCount() === 1
? getSelectedBlock()
: getMultiSelectedBlocks(),
blockPatternCategories: getSettings().__experimentalBlockPatternCategories,
};
} ),

withDispatch( ( dispatch ) => {
const {
updateSettings,
} = dispatch( 'core/block-editor' );

const {
setIsInserterOpened,
} = dispatch( 'core/edit-post' );

const {
createErrorNotice,
createSuccessNotice,
} = dispatch( 'core/notices' );

return {
createErrorNotice,
createSuccessNotice,
setIsInserterOpened,
updateSettings,
};
} ),

] )( CoBlocksBlockPatternsModal );
14 changes: 14 additions & 0 deletions src/extensions/block-patterns/styles/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@import "../../../styles/editor/core/colors";
@import "../../../styles/editor/core/variables";

.coblocks-block-patterns__modal {
.components-base-control__field {
margin-bottom: $grid-unit-20;
}
}

.coblocks-block-patterns__preview {
margin-bottom: $grid-unit-30;
padding: 9px;
border: $border-width solid $gray-700;
}

0 comments on commit e65e9f8

Please sign in to comment.