Skip to content

Commit

Permalink
Block Library: Implement Template Part block editing 2. (#19203)
Browse files Browse the repository at this point in the history
* Template Loader: Create auto drafts for template parts in the currently edited post.

* Core Data: Evaluate optimized function edits on save.

* Core Data: Implement hooks for syncing blocks to entities.

* Template Part: Implement edit component.

* Editor: Make entity save labels clearer.

* Block Editor: Go back to controlled inner blocks approach.

* Template Parts: Add missing CPT REST config params.

* Inner Blocks: Guard against trying to reset selection when replacing inner blocks with an empty list.

* Block Library: Modularize Template Part block editing and add a placeholder for choosing or creating template parts.

* Core Data: Add missing default argument to `useEntityBlockEditor`.

* Template Part Block: Show preview when choosing an existing template part.

* Template Loader: Don't load template parts if experiment is off.

* Template Part: Fix mobile placeholder layout.

* Template Part: Disable edit as HTML support.

* Template Part: Swap `onChange` for `onInput`.

* URL: Add editor's slug cleaning utility and use it in the template part block.

* Entity Provider: Clarify why we don't import `select` directly.

* Core Data: Remove unused block editor dependency.

* Template Part: Update placeholder layout.

* Inner Blocks: Make blocks prop experimental.

* Core Data: Pass edited entity record to optimized function edits on evaluation.
  • Loading branch information
epiqueras authored Jan 9, 2020
1 parent e026b4b commit 9d4bbef
Show file tree
Hide file tree
Showing 22 changed files with 504 additions and 23 deletions.
22 changes: 22 additions & 0 deletions lib/template-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,25 @@ function gutenberg_viewport_meta_tag() {
function gutenberg_strip_php_suffix( $template_file ) {
return preg_replace( '/\.php$/', '', $template_file );
}

/**
* Extends default editor settings to enable template and template part editing.
*
* @param array $settings Default editor settings.
*
* @return array Filtered editor settings.
*/
function gutenberg_template_loader_filter_block_editor_settings( $settings ) {
if ( ! post_type_exists( 'wp_template' ) || ! post_type_exists( 'wp_template_part' ) ) {
return $settings;
}

// Create template part auto-drafts for the edited post.
foreach ( parse_blocks( get_post()->post_content ) as $block ) {
create_auto_draft_for_template_part_block( $block );
}

// TODO: Set editing mode and current template ID for editing modes support.
return $settings;
}
add_filter( 'block_editor_settings', 'gutenberg_template_loader_filter_block_editor_settings' );
2 changes: 2 additions & 0 deletions lib/template-parts.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ function gutenberg_register_template_part_post_type() {
'show_in_menu' => 'themes.php',
'show_in_admin_bar' => false,
'show_in_rest' => true,
'rest_base' => 'template-parts',
'map_meta_cap' => true,
'supports' => array(
'title',
'slug',
'editor',
'revisions',
'custom-fields',
),
);

Expand Down
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 36 additions & 3 deletions packages/block-editor/src/components/inner-blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ class InnerBlocks extends Component {
}

componentDidMount() {
const { templateLock, block } = this.props;
const {
block,
templateLock,
__experimentalBlocks,
replaceInnerBlocks,
} = this.props;
const { innerBlocks } = block;
// Only synchronize innerBlocks with template if innerBlocks are empty or a locking all exists directly on the block.
if ( innerBlocks.length === 0 || templateLock === 'all' ) {
Expand All @@ -48,10 +53,22 @@ class InnerBlocks extends Component {
templateInProcess: false,
} );
}

// Set controlled blocks value from parent, if any.
if ( __experimentalBlocks ) {
replaceInnerBlocks( __experimentalBlocks );
}
}

componentDidUpdate( prevProps ) {
const { template, block, templateLock } = this.props;
const {
block,
templateLock,
template,
isLastBlockChangePersistent,
onInput,
onChange,
} = this.props;
const { innerBlocks } = block;

this.updateNestedSettings();
Expand All @@ -62,6 +79,14 @@ class InnerBlocks extends Component {
this.synchronizeBlocksWithTemplate();
}
}

// Sync with controlled blocks value from parent, if possible.
if ( prevProps.block.innerBlocks !== innerBlocks ) {
const resetFunc = isLastBlockChangePersistent ? onChange : onInput;
if ( resetFunc ) {
resetFunc( innerBlocks );
}
}
}

/**
Expand Down Expand Up @@ -141,6 +166,7 @@ InnerBlocks = compose( [
getBlockRootClientId,
getTemplateLock,
isNavigationMode,
isLastBlockChangePersistent,
} = select( 'core/block-editor' );
const { clientId, isSmallScreen } = ownProps;
const block = getBlock( clientId );
Expand All @@ -152,6 +178,7 @@ InnerBlocks = compose( [
hasOverlay: block.name !== 'core/template' && ! isBlockSelected( clientId ) && ! hasSelectedInnerBlock( clientId, true ),
parentLock: getTemplateLock( rootClientId ),
enableClickThrough: isNavigationMode() || isSmallScreen,
isLastBlockChangePersistent: isLastBlockChangePersistent(),
};
} ),
withDispatch( ( dispatch, ownProps ) => {
Expand All @@ -163,7 +190,13 @@ InnerBlocks = compose( [

return {
replaceInnerBlocks( blocks ) {
replaceInnerBlocks( clientId, blocks, block.innerBlocks.length === 0 && templateInsertUpdatesSelection );
replaceInnerBlocks(
clientId,
blocks,
block.innerBlocks.length === 0 &&
templateInsertUpdatesSelection &&
blocks.length !== 0
);
},
updateNestedSettings( settings ) {
dispatch( updateBlockListSettings( clientId, settings ) );
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
@import "./subhead/editor.scss";
@import "./table/editor.scss";
@import "./tag-cloud/editor.scss";
@import "./template-part/editor.scss";
@import "./text-columns/editor.scss";
@import "./verse/editor.scss";
@import "./video/editor.scss";
Expand Down
3 changes: 3 additions & 0 deletions packages/block-library/src/template-part/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@
"theme": {
"type": "string"
}
},
"supports": {
"html": false
}
}
3 changes: 0 additions & 3 deletions packages/block-library/src/template-part/edit.js

This file was deleted.

50 changes: 50 additions & 0 deletions packages/block-library/src/template-part/edit/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* WordPress dependencies
*/
import { useRef, useEffect } from '@wordpress/element';
import { EntityProvider } from '@wordpress/core-data';

/**
* Internal dependencies
*/
import useTemplatePartPost from './use-template-part-post';
import TemplatePartInnerBlocks from './inner-blocks';
import TemplatePartPlaceholder from './placeholder';

export default function TemplatePartEdit( {
attributes: { postId: _postId, slug, theme },
setAttributes,
} ) {
const initialPostId = useRef( _postId );
const initialSlug = useRef( slug );
const initialTheme = useRef( theme );

// Resolve the post ID if not set, and load its post.
const postId = useTemplatePartPost( _postId, slug, theme );

// Set the post ID, once found, so that edits persist.
useEffect( () => {
if (
( initialPostId.current === undefined || initialPostId.current === null ) &&
postId !== undefined &&
postId !== null
) {
setAttributes( { postId } );
}
}, [ postId ] );

if ( postId ) {
// Part of a template file, post ID already resolved.
return (
<EntityProvider kind="postType" type="wp_template_part" id={ postId }>
<TemplatePartInnerBlocks />
</EntityProvider>
);
}
if ( ! initialSlug.current && ! initialTheme.current ) {
// Fresh new block.
return <TemplatePartPlaceholder setAttributes={ setAttributes } />;
}
// Part of a template file, post ID not resolved yet.
return null;
}
22 changes: 22 additions & 0 deletions packages/block-library/src/template-part/edit/inner-blocks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* WordPress dependencies
*/
import { useEntityBlockEditor } from '@wordpress/core-data';
import { InnerBlocks } from '@wordpress/block-editor';

export default function TemplatePartInnerBlocks() {
const [ blocks, onInput, onChange ] = useEntityBlockEditor(
'postType',
'wp_template_part',
{
initialEdits: { status: 'publish' },
}
);
return (
<InnerBlocks
__experimentalBlocks={ blocks }
onInput={ onInput }
onChange={ onChange }
/>
);
}
122 changes: 122 additions & 0 deletions packages/block-library/src/template-part/edit/placeholder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* WordPress dependencies
*/
import { useEntityBlockEditor, EntityProvider } from '@wordpress/core-data';
import { __ } from '@wordpress/i18n';
import { BlockPreview } from '@wordpress/block-editor';
import { useState, useCallback } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { cleanForSlug } from '@wordpress/url';
import { Placeholder, TextControl, Button } from '@wordpress/components';

/**
* Internal dependencies
*/
import useTemplatePartPost from './use-template-part-post';

function TemplatePartPreview() {
const [ blocks ] = useEntityBlockEditor( 'postType', 'wp_template_part' );
return (
<div className="wp-block-template-part__placeholder-preview">
<div className="wp-block-template-part__placeholder-preview-title">
{ __( 'Preview' ) }
</div>
<BlockPreview blocks={ blocks } />
</div>
);
}

export default function TemplatePartPlaceholder( { setAttributes } ) {
const [ slug, _setSlug ] = useState();
const [ theme, setTheme ] = useState();
const [ help, setHelp ] = useState();

// Try to find an existing template part.
const postId = useTemplatePartPost( null, slug, theme );

// If found, get its preview.
const preview = useSelect(
( select ) => {
if ( ! postId ) {
return;
}
const templatePart = select( 'core' ).getEntityRecord(
'postType',
'wp_template_part',
postId
);
if ( templatePart ) {
return (
<EntityProvider kind="postType" type="wp_template_part" id={ postId }>
<TemplatePartPreview />
</EntityProvider>
);
}
},
[ postId ]
);

const setSlug = useCallback( ( nextSlug ) => {
_setSlug( nextSlug );
setHelp( cleanForSlug( nextSlug ) );
}, [] );

const { saveEntityRecord } = useDispatch( 'core' );
const onChooseOrCreate = useCallback( async () => {
const nextAttributes = { slug, theme };
if ( postId !== undefined && postId !== null ) {
// Existing template part found.
nextAttributes.postId = postId;
} else {
// Create a new template part.
try {
const cleanSlug = cleanForSlug( slug );
const templatePart = await saveEntityRecord(
'postType',
'wp_template_part',
{
title: cleanSlug,
status: 'publish',
slug: cleanSlug,
meta: { theme },
}
);
nextAttributes.postId = templatePart.id;
} catch ( err ) {
setHelp( __( 'Error adding template.' ) );
}
}
setAttributes( nextAttributes );
}, [ postId, slug, theme ] );
return (
<Placeholder
icon="layout"
label={ __( 'Template Part' ) }
instructions={ __(
'Choose a template part by slug and theme, or create a new one.'
) }
>
<div className="wp-block-template-part__placeholder-input-container">
<TextControl
label={ __( 'Slug' ) }
placeholder={ __( 'header' ) }
value={ slug }
onChange={ setSlug }
help={ help }
className="wp-block-template-part__placeholder-input"
/>
<TextControl
label={ __( 'Theme' ) }
placeholder={ __( 'twentytwenty' ) }
value={ theme }
onChange={ setTheme }
className="wp-block-template-part__placeholder-input"
/>
</div>
{ preview }
<Button isPrimary disabled={ ! slug || ! theme } onClick={ onChooseOrCreate }>
{ postId ? __( 'Choose' ) : __( 'Create' ) }
</Button>
</Placeholder>
);
}
Loading

0 comments on commit 9d4bbef

Please sign in to comment.