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: Operate on template CPT posts and add a default template with post title and content blocks. #16485

Closed
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

require dirname( __FILE__ ) . '/compat.php';
require dirname( __FILE__ ) . '/blocks.php';
require dirname( __FILE__ ) . '/templates.php';
require dirname( __FILE__ ) . '/client-assets.php';
require dirname( __FILE__ ) . '/demo.php';
require dirname( __FILE__ ) . '/widgets.php';
Expand Down
53 changes: 53 additions & 0 deletions lib/templates.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php
/**
* Register Gutenberg core block editor templates.
*
* @package gutenberg
*/

/**
* Registers Gutenberg core block editor templates.
*/
function gutenberg_register_templates() {
register_post_type(
'wp_template',
array(
'labels' => array(
'name' => __( 'Templates', 'gutenberg' ),
),
'show_in_rest' => true,
)
);

$template_query = new WP_Query(
array(
'post_type' => 'wp_template',
'name' => 'single-post',
)
);

$template;
if ( ! $template_query->have_posts() ) {
$template_id = wp_insert_post(
array(
'post_type' => 'wp_template',
'post_name' => 'single-post',
'post_content' => '<!-- wp:post-title /--><!-- wp:post-content --><!-- wp:paragraph --><p></p><!-- /wp:paragraph --><!-- /wp:post-content -->',
)
);
$template = get_post( $template_id );
} else {
$template = $template_query->get_posts();
$template = $template[0];
}

if ( isset( $_GET['gutenberg-demo'] ) ) {
ob_start();
include gutenberg_dir_path() . 'post-content.php';
$template->post_content = ob_get_clean();
}

$post_type_object = get_post_type_object( 'post' );
$post_type_object->template = $template;
}
add_action( 'init', 'gutenberg_register_templates' );
7 changes: 5 additions & 2 deletions packages/block-editor/src/components/block-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,17 +262,20 @@ export default compose( [
getMultiSelectedBlockClientIds,
hasMultiSelection,
getGlobalBlockCount,
getFirstAncestorWithZoomSupport,
} = select( 'core/block-editor' );
const selectedBlockClientId = getSelectedBlockClientId();
const zoomedBlockId = getFirstAncestorWithZoomSupport( selectedBlockClientId );

const { rootClientId } = ownProps;
const rootClientId = ownProps.rootClientId || zoomedBlockId;

return {
blockClientIds: getBlockOrder( rootClientId ),
selectionStart: getMultiSelectedBlocksStartClientId(),
selectionEnd: getMultiSelectedBlocksEndClientId(),
isSelectionEnabled: isSelectionEnabled(),
isMultiSelecting: isMultiSelecting(),
selectedBlockClientId: getSelectedBlockClientId(),
selectedBlockClientId,
multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(),
hasMultiSelection: hasMultiSelection(),
enableAnimation: getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD,
Expand Down
35 changes: 35 additions & 0 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1405,3 +1405,38 @@ export function __experimentalGetLastBlockAttributeChanges( state ) {
function getReusableBlocks( state ) {
return get( state, [ 'settings', '__experimentalReusableBlocks' ], EMPTY_ARRAY );
}

/**
* Returns the first ancestor of the specified block, that supports zoom.
* Returns null if there isn't one.
*
* @param {Object} state Editor state.
* @param {string} clientId The block's client ID.
*
* @return {?string} The zoom supporting ancestor's client ID or null if there isn't one.
*/
export const getFirstAncestorWithZoomSupport = createSelector(
( state, clientId ) => {
let ancestorClientId = clientId;
while ( ancestorClientId ) {
const blockName = getBlockName( state, ancestorClientId );
if ( ! blockName ) {
return null;
}

const blockType = getBlockType( blockName );
if ( ! blockType ) {
return null;
}

if ( hasBlockSupport( blockType, 'zoom', false ) ) {
return ancestorClientId;
}

ancestorClientId = getBlockRootClientId( state, ancestorClientId );
}

return ancestorClientId;
},
( state ) => [ state.blocks.byClientId, getBlockTypes(), state.blocks.parents ]
);
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';

// Top-level template blocks.
import * as postTitle from './post-title';
import * as postContent from './post-content';

/**
* Function to register core blocks provided by the block editor.
*
Expand Down Expand Up @@ -124,6 +128,10 @@ export const registerCoreBlocks = () => {
textColumns,
verse,
video,

// Register top-level template blocks.
postTitle,
postContent,
].forEach( ( block ) => {
if ( ! block ) {
return;
Expand Down
8 changes: 8 additions & 0 deletions packages/block-library/src/post-content/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "core/post-content",
"category": "common",
"supports": {
"multiple": false,
"zoom": true
}
}
8 changes: 8 additions & 0 deletions packages/block-library/src/post-content/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* WordPress dependencies
*/
import { InnerBlocks } from '@wordpress/block-editor';

export default function PostContentEdit() {
return <InnerBlocks templateLock={ false } />;
}
19 changes: 19 additions & 0 deletions packages/block-library/src/post-content/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

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

const { name } = metadata;

export { metadata, name };

export const settings = {
title: __( 'Post Content' ),
edit,
};
11 changes: 11 additions & 0 deletions packages/block-library/src/post-title/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "core/post-title",
"category": "common",
"attributes": {
"title": {
"type": "string",
"source": "post",
"attribute": "title"
Copy link
Member

Choose a reason for hiding this comment

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

I might think to call this property, particularly if we just consider the post an object from which we're picking property values. Also avoids ambiguity on block attributes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went with attributes because all the selectors use "post attributes" as the terminology.

Copy link
Member

Choose a reason for hiding this comment

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

I went with attributes because all the selectors use "post attributes" as the terminology.

Hmm, that's fair. In retrospect I think they might have been more canonically referred to as properties of the post, but I'm fine targeting consistency if even for consistency's sake.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I like "properties" more too.

}
}
}
24 changes: 24 additions & 0 deletions packages/block-library/src/post-title/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* WordPress dependencies
*/
import { RichText } from '@wordpress/block-editor';

export default function PostTitleEdit( {
attributes: { title },
setAttributes,
} ) {
return (
<RichText
value={ title }
onChange={ ( value ) => setAttributes( { title: value } ) }
tagName="h1"
placeholder={ __( 'Title' ) }
formattingControls={ [] }
/>
);
}
19 changes: 19 additions & 0 deletions packages/block-library/src/post-title/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

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

const { name } = metadata;

export { metadata, name };

export const settings = {
title: __( 'Post Title' ),
edit,
};
18 changes: 14 additions & 4 deletions packages/blocks/src/api/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,22 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) {
return blocks;
}

return map( template, ( [ name, attributes, innerBlocksTemplate ], index ) => {
return map( template, ( templateBlock, index ) => {
const [ name, attributes, _innerBlocksTemplate ] = Array.isArray( templateBlock ) ?
templateBlock :
[ templateBlock.name, templateBlock.attributes, templateBlock.innerBlocks ];
const block = blocks[ index ];
let innerBlocksTemplate = _innerBlocksTemplate;

if ( block && block.name === name ) {
const innerBlocks = synchronizeBlocksWithTemplate( block.innerBlocks, innerBlocksTemplate );
return { ...block, innerBlocks };
if ( block ) {
if ( block.name === name ) {
const innerBlocks = synchronizeBlocksWithTemplate( block.innerBlocks, innerBlocksTemplate );
return { ...block, innerBlocks };
}

if ( 'core/post-content' === name ) {
innerBlocksTemplate = blocks;
}
}

// To support old templates that were using the "children" format
Expand Down
6 changes: 1 addition & 5 deletions packages/edit-post/src/components/visual-editor/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
/**
* WordPress dependencies
*/
import {
PostTitle,
VisualEditorGlobalKeyboardShortcuts,
} from '@wordpress/editor';
import { VisualEditorGlobalKeyboardShortcuts } from '@wordpress/editor';
import {
WritingFlow,
ObserveTyping,
Expand All @@ -30,7 +27,6 @@ function VisualEditor() {
<WritingFlow>
<ObserveTyping>
<CopyHandler>
<PostTitle />
<BlockList />
</CopyHandler>
</ObserveTyping>
Expand Down
24 changes: 20 additions & 4 deletions packages/editor/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { dispatch, select, apiFetch } from '@wordpress/data-controls';
import {
parse,
synchronizeBlocksWithTemplate,
serialize,
} from '@wordpress/blocks';
import isShallowEqual from '@wordpress/is-shallow-equal';

Expand Down Expand Up @@ -170,9 +171,11 @@ export function* setupEditor( post, edits, template ) {
let blocks = parse( content );

// Apply a template for new posts only, if exists.
const isNewPost = post.status === 'auto-draft';
if ( isNewPost && template ) {
blocks = synchronizeBlocksWithTemplate( blocks, template );
if ( template ) {
blocks = synchronizeBlocksWithTemplate(
blocks,
template.post_content ? parse( template.post_content ) : template
);
}

yield resetPost( post );
Expand Down Expand Up @@ -474,11 +477,24 @@ export function* savePost( options = {} ) {
'getCurrentPost'
);

const editedPostContent = yield select(
let editedPostContent = yield select(
STORE_KEY,
'getEditedPostContent'
);

const template = ( yield select( STORE_KEY, 'getEditorSettings' ) ).template;
if ( template && template.post_content ) {
yield apiFetch( {
path: `/wp/v2/${ template.post_type }/${ template.ID }`,
method: 'PUT',
data: {
content: editedPostContent,
},
} );
const postContentBlock = ( yield select( STORE_KEY, 'getBlocksForSerialization' ) ).find( ( block ) => block.name === 'core/post-content' );
editedPostContent = postContentBlock ? serialize( postContentBlock.innerBlocks ) : '';
}

let toSend = {
...edits,
content: editedPostContent,
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/src/store/block-sources/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* Internal dependencies
*/
import * as post from './post';
import * as meta from './meta';

export { meta };
export { post, meta };
46 changes: 46 additions & 0 deletions packages/editor/src/store/block-sources/post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* External dependencies
*/
import { get, set } from 'lodash';

/**
* WordPress dependencies
*/
import { select } from '@wordpress/data-controls';

/**
* Internal dependencies
*/
import { editPost } from '../actions';
import { EDIT_MERGE_PROPERTIES } from '../constants';

export function* getDependencies() {
return {
post: yield select( 'core/editor', 'getCurrentPost' ),
content: yield select( 'core/editor', 'getEditedPostContent' ),
edits: yield select( 'core/editor', 'getPostEdits' ),
};
}

export function apply( { attribute }, { post, content, edits } ) {
if ( 'content' === attribute ) {
return content;
}

if ( undefined === get( edits, attribute ) ) {
return get( post, attribute );
}

if ( EDIT_MERGE_PROPERTIES.has( attribute ) ) {
return {
...get( post, attribute ),
...get( edits, attribute ),
};
}

return get( edits, attribute );
}

export function* update( { attribute }, value ) {
yield editPost( set( {}, attribute, value ) );
}
Loading