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: Introduce a new dynamic templates mode and add basic post title and content blocks. #17263

Closed
wants to merge 12 commits into from
Closed
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: 2 additions & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ function gutenberg_reregister_core_block_types() {
'social-link.php' => gutenberg_get_registered_social_link_blocks(),
'tag-cloud.php' => 'core/tag-cloud',
'site-title.php' => 'core/site-title',
'post-title.php' => 'core/post-title',
'post-content.php' => 'core/post-content',
);

$registry = WP_Block_Type_Registry::get_instance();
Expand Down
57 changes: 57 additions & 0 deletions lib/template-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,60 @@ function gutenberg_viewport_meta_tag() {
function gutenberg_strip_php_suffix( $template_file ) {
return preg_replace( '/\.php$/', '', $template_file );
}

/**
* Extends default editor settings to add the right template ID and enable editing modes.
*
* @param array $settings Default editor settings.
*
* @return array Filtered editor settings.
*/
function gutenberg_template_loader_filter_block_editor_settings( $settings ) {
global $wp_query, $_wp_current_template_post;

// Run template resolution manually to trigger our override filters.
$tag_templates = array(
'is_embed' => 'get_embed_template',
'is_404' => 'get_404_template',
'is_search' => 'get_search_template',
'is_front_page' => 'get_front_page_template',
'is_home' => 'get_home_template',
'is_privacy_policy' => 'get_privacy_policy_template',
'is_post_type_archive' => 'get_post_type_archive_template',
'is_tax' => 'get_taxonomy_template',
'is_attachment' => 'get_attachment_template',
'is_single' => 'get_single_template',
'is_page' => 'get_page_template',
'is_singular' => 'get_singular_template',
'is_category' => 'get_category_template',
'is_tag' => 'get_tag_template',
'is_author' => 'get_author_template',
'is_date' => 'get_date_template',
'is_archive' => 'get_archive_template',
);
$template = false;
// Loop through each of the template conditionals, and find the appropriate template file.
$post = get_post();
$wp_query->parse_query( 'p=' . $post->ID . '&preview=true' );
foreach ( $tag_templates as $tag => $template_getter ) {
if ( call_user_func( $tag ) ) {
$template = call_user_func( $template_getter );
}

if ( $template ) {
if ( 'is_attachment' === $tag ) {
remove_filter( 'the_content', 'prepend_attachment' );
}
break;
}
}
if ( ! $template ) {
$template = get_index_template();
}
$template = apply_filters( 'template_include', $template );

$settings['templateId'] = $_wp_current_template_post->ID;
$settings['editingMode'] = 'post-content';
return $settings;
}
add_filter( 'block_editor_settings', 'gutenberg_template_loader_filter_block_editor_settings' );
2 changes: 1 addition & 1 deletion packages/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ $z-layers: (
".wp-block-cover__inner-container": 1, // InnerBlocks area inside cover image block
".wp-block-cover.has-background-dim::before": 1, // Overlay area inside block cover need to be higher than the video background.
".wp-block-cover__video-background": 0, // Video background inside cover block.
".wp-block-site-title__save-button": 1,
".wp-block-custom-entity__save-button": 1,

// Active pill button
".components-button.is-button {:focus or .is-primary}": 1,
Expand Down
26 changes: 24 additions & 2 deletions packages/block-editor/src/components/inner-blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class InnerBlocks extends Component {
}

componentDidMount() {
const { templateLock, block } = this.props;
const { block, templateLock, value, 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 @@ -49,10 +49,22 @@ class InnerBlocks extends Component {
templateInProcess: false,
} );
}

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

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

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

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

/**
Expand Down Expand Up @@ -149,6 +169,7 @@ InnerBlocks = compose( [
getBlockListSettings,
getBlockRootClientId,
getTemplateLock,
isLastBlockChangePersistent,
} = select( 'core/block-editor' );
const { clientId } = ownProps;
const block = getBlock( clientId );
Expand All @@ -159,6 +180,7 @@ InnerBlocks = compose( [
blockListSettings: getBlockListSettings( clientId ),
hasOverlay: block.name !== 'core/template' && ! isBlockSelected( clientId ) && ! hasSelectedInnerBlock( clientId, true ),
parentLock: getTemplateLock( rootClientId ),
isLastBlockChangePersistent: isLastBlockChangePersistent(),
};
} ),
withDispatch( ( dispatch, ownProps ) => {
Expand Down
8 changes: 7 additions & 1 deletion packages/block-library/src/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
@import "./text-columns/editor.scss";
@import "./verse/editor.scss";
@import "./video/editor.scss";
@import "./site-title/editor.scss";

/**
* Import styles from internal editor components used by the blocks.
Expand All @@ -57,3 +56,10 @@
margin-top: $default-block-margin;
margin-bottom: $default-block-margin;
}

.wp-block-custom-entity__save-button {
position: absolute;
right: 0;
top: 0;
z-index: z-index(".wp-block-custom-entity__save-button");
}
6 changes: 5 additions & 1 deletion packages/block-library/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ import * as socialLink from './social-link';

// Full Site Editing Blocks
import * as siteTitle from './site-title';
import * as postTitle from './post-title';
import * as postContent from './post-content';

/**
* Function to register an individual block.
Expand Down Expand Up @@ -182,7 +184,9 @@ export const __experimentalRegisterExperimentalCoreBlocks =
...socialLink.sites,

// Register Full Site Editing Blocks.
...( __experimentalEnableFullSiteEditing ? [ siteTitle ] : [] ),
...( __experimentalEnableFullSiteEditing ?
[ siteTitle, postTitle, postContent ] :
[] ),
].forEach( registerBlock );
} :
undefined;
4 changes: 4 additions & 0 deletions packages/block-library/src/post-content/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "core/post-content",
"category": "layout"
}
62 changes: 62 additions & 0 deletions packages/block-library/src/post-content/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* WordPress dependencies
*/
import {
useEntityId,
useEntityProp,
__experimentalUseEntitySaving,
} from '@wordpress/core-data';
import { useMemo, useCallback } from '@wordpress/element';
import { parse } from '@wordpress/blocks';
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { InnerBlocks } from '@wordpress/block-editor';
import { serializeBlocks } from '@wordpress/editor';

export default function PostContentEdit() {
const postId = useEntityId( 'postType', 'post' );
const [ content, _setContent ] = useEntityProp( 'postType', 'post', 'content' );
const initialBlocks = useMemo( () => {
if ( postId && typeof content !== 'function' ) {
const parsedContent = parse( content );
return parsedContent.length ? parsedContent : undefined;
}
}, [] );
const [ blocks = initialBlocks, setBlocks ] = useEntityProp(
'postType',
'post',
'blocks'
);
const [ isDirty, isSaving, save ] = __experimentalUseEntitySaving(
'postType',
'post',
'content'
);
const saveContent = useCallback( () => {
_setContent( content( { blocks } ) );
save();
}, [ content, blocks ] );
const setContent = useCallback( () => {
_setContent( ( { blocks: blocksForSerialization = [] } ) =>
serializeBlocks( blocksForSerialization )
);
}, [] );
return postId ? (
<>
<Button
isPrimary
className="wp-block-custom-entity__save-button"
disabled={ ! isDirty || ! content }
isBusy={ isSaving }
onClick={ saveContent }
>
{ __( 'Update' ) }
</Button>
<div className="entry-content">
<InnerBlocks value={ blocks } onChange={ setBlocks } onInput={ setContent } />
</div>
</>
) : (
'Post Content Placeholder'
);
}
11 changes: 11 additions & 0 deletions packages/block-library/src/post-content/icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* WordPress dependencies
*/
import { SVG, Path } from '@wordpress/components';

export default (
<SVG xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24">
<Path fill="none" d="M0 0h24v24H0V0z" />
<Path d="M3 15h18v-2H3v2zm0 4h18v-2H3v2zm0-8h18V9H3v2zm0-6v2h18V5H3z" />
</SVG>
);
20 changes: 20 additions & 0 deletions packages/block-library/src/post-content/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

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

const { name } = metadata;
export { metadata, name };

export const settings = {
title: __( 'Post Content' ),
icon,
edit,
};
38 changes: 38 additions & 0 deletions packages/block-library/src/post-content/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
/**
* Server-side rendering of the `core/post-content` block.
*
* @package WordPress
*/

/**
* Renders the `core/post-content` block on the server.
*
* @return string Returns the filtered post content of the current post.
*/
function render_block_core_post_content() {
// TODO: Without this temporary fix, an infinite loop can occur.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's add inline comments as to how this fixes the infinite loop and why it occurs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@felixarntz

Put in a (temporary) fix for a possible infinite loop. This is not a proper solution, and we might need to find a better way to fix it. I haven't been able to dig to its roots, but basically the block parser continues to iterate the wp_template endlessly when processing the content in a REST API request (which also happens during admin initialization because of preloading).

Interesting, are you sure you didn't have nested post content blocks?

What are the repercussions of this fix. Will returning an empty string like this affect anything? If not, I guess we can keep it.

if ( is_admin() || defined( 'REST_REQUEST' ) ) {
return '';
}

if ( ! in_the_loop() ) {
rewind_posts();
the_post();
}

return '<div class="entry-content">' . apply_filters( 'the_content', str_replace( ']]>', ']]&gt;', get_the_content() ) ) . '</div>';
}

/**
* Registers the `core/post-content` block on the server.
*/
function register_block_core_post_content() {
register_block_type(
'core/post-content',
array(
'render_callback' => 'render_block_core_post_content',
)
);
}
add_action( 'init', 'register_block_core_post_content' );
4 changes: 4 additions & 0 deletions packages/block-library/src/post-title/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "core/post-title",
"category": "layout"
}
51 changes: 51 additions & 0 deletions packages/block-library/src/post-title/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* WordPress dependencies
*/
import {
useEntityId,
useEntityProp,
__experimentalUseEntitySaving,
} from '@wordpress/core-data';
import { useCallback } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { RichText } from '@wordpress/block-editor';
import { cleanForSlug } from '@wordpress/editor';

const saveProps = [ 'title', 'slug' ];
export default function PostTitleEdit() {
const postId = useEntityId( 'postType', 'post' );
const [ title, _setTitle ] = useEntityProp( 'postType', 'post', 'title' );
const [ , setSlug ] = useEntityProp( 'postType', 'post', 'slug' );
Copy link
Member

Choose a reason for hiding this comment

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

The hard-coding of post here is probably just temporary, but we need to figure out a way to do it right, since currently this only works for posts of post type 'post' and causes an error on other post types.

The problem is that the overall current post being edited appears to be overridden by this implementation, causing getCurrentPost() to return the wp_template instead of the actual post being edited. Otherwise we could have used withSelect() to get the current post type, but that wouldn't work.

I'm a bit wary of overriding the current post context in Gutenberg to become a wp_template post when actually editing a "real" post, since it could break expectations by other plugins. We should probably think about a way we expose the current wp_template through a set of new API functions (e.g. getCurrentTemplate() etc.), where we could transform the editor accordingly based on whether these include a wp_template post or not (if not, it would be just like today).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The editor already supports editing multiple post types and the editor store has a getPostType selector.

Copy link
Member

Choose a reason for hiding this comment

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

Are you referring to getCurrentPostType()? It still returns wp_template here when I use it. How do I get the post type of the actual post being edited? It's hard-coded to post here, so we need to replace it with a function that dynamically gets the correct post type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I thought you were asking how to get the post type of the template. (The template is the actual post being edited after all.)

You can get the post that uses the template through editor settings:

const { postId, postType, templateId } = getEditorSettings()

const [ isDirty, isSaving, save ] = __experimentalUseEntitySaving(
'postType',
'post',
saveProps
);
const setTitle = useCallback( ( value ) => {
_setTitle( value );
setSlug( cleanForSlug( value ) );
}, [] );
return postId ? (
<>
<Button
isPrimary
className="wp-block-custom-entity__save-button"
disabled={ ! isDirty || ! title }
isBusy={ isSaving }
onClick={ save }
>
{ __( 'Update' ) }
</Button>
<RichText
tagName="h1"
placeholder={ __( 'Title' ) }
value={ title }
onChange={ setTitle }
allowedFormats={ [] }
/>
</>
) : (
'Post Title Placeholder'
);
}
11 changes: 11 additions & 0 deletions packages/block-library/src/post-title/icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* WordPress dependencies
*/
import { SVG, Path } from '@wordpress/components';

export default (
<SVG xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24">
<Path fill="none" d="M0 0h24v24H0V0z" />
<Path d="M5 4v3h5.5v12h3V7H19V4H5z" />
</SVG>
);
Loading