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

[Site Editor]: Expand the template types that can be added - single custom post type and specific posts templates #41189

Merged
merged 20 commits into from
Jun 21, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,75 @@ public function create_item( $request ) {
);
}

/**
* Updates a single template.
*
* @since 5.8.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 update_item( $request ) {
$template = gutenberg_get_block_template( $request['id'], $this->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.

I have only updated to use gutenberg_get_block_template. The function is copied from core.

if ( ! $template ) {
return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) );
}

$post_before = get_post( $template->wp_id );

if ( isset( $request['source'] ) && 'theme' === $request['source'] ) {
wp_delete_post( $template->wp_id, true );
$request->set_param( 'context', 'edit' );

$template = gutenberg_get_block_template( $request['id'], $this->post_type );
$response = $this->prepare_item_for_response( $template, $request );

return rest_ensure_response( $response );
}

$changes = $this->prepare_item_for_database( $request );

if ( is_wp_error( $changes ) ) {
return $changes;
}

if ( 'custom' === $template->source ) {
$update = true;
$result = wp_update_post( wp_slash( (array) $changes ), false );
} else {
$update = false;
$post_before = null;
$result = wp_insert_post( wp_slash( (array) $changes ), false );
}

if ( is_wp_error( $result ) ) {
if ( 'db_update_error' === $result->get_error_code() ) {
$result->add_data( array( 'status' => 500 ) );
} else {
$result->add_data( array( 'status' => 400 ) );
}
return $result;
}

$template = gutenberg_get_block_template( $request['id'], $this->post_type );
$fields_update = $this->update_additional_fields_for_object( $template, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}

$request->set_param( 'context', 'edit' );

$post = get_post( $template->wp_id );
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
do_action( "rest_after_insert_{$this->post_type}", $post, $request, false );

wp_after_insert_post( $post, $update, $post_before );

$response = $this->prepare_item_for_response( $template, $request );

return rest_ensure_response( $response );
}

/**
* Prepares a single template for create or update.
*
Expand Down
15 changes: 15 additions & 0 deletions lib/compat/wordpress-6.1/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,18 @@ function gutenberg_update_templates_template_parts_rest_controller( $args, $post
return $args;
}
add_filter( 'register_post_type_args', 'gutenberg_update_templates_template_parts_rest_controller', 10, 2 );


/**
* Add the post type's `icon`(menu_icon) in the response.
* When we backport this change we will need to add the
* `icon` to WP_REST_Post_Types_Controller schema.
*
* @param WP_REST_Response $response The response object.
* @param WP_Post_Type $post_type The original post type object.
*/
function gutenberg_update_post_types_rest_response( $response, $post_type ) {
$response->data['icon'] = $post_type->menu_icon;
return $response;
}
add_filter( 'rest_prepare_post_type', 'gutenberg_update_post_types_rest_response', 10, 2 );
1 change: 0 additions & 1 deletion packages/base-styles/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@
}
}


/**
* Allows users to opt-out of animations via OS-level preferences.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/**
* WordPress dependencies
*/
import { useState, useMemo, useEffect } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import {
Button,
Flex,
FlexItem,
Modal,
SearchControl,
TextHighlight,
__experimentalText as Text,
__experimentalHeading as Heading,
__unstableComposite as Composite,
__unstableUseCompositeState as useCompositeState,
__unstableCompositeItem as CompositeItem,
} from '@wordpress/components';
import { useDebounce } from '@wordpress/compose';
import { useEntityRecords } from '@wordpress/core-data';

/**
* Internal dependencies
*/
import { mapToIHasNameAndId } from './utils';

const EMPTY_ARRAY = [];
const BASE_QUERY = {
order: 'asc',
_fields: 'id,title,slug,link',
context: 'view',
};

function SuggestionListItem( {
suggestion,
search,
onSelect,
entityForSuggestions,
composite,
} ) {
const baseCssClass =
'edit-site-custom-template-modal__suggestions_list__list-item';
return (
<CompositeItem
role="option"
as={ Button }
{ ...composite }
className={ baseCssClass }
onClick={ () => {
const title = sprintf(
// translators: Represents the title of a user's custom template in the Site Editor, where %1$s is the singular name of a post type and %2$s is the name of the post, e.g. "Post: Hello, WordPress"
__( '%1$s: %2$s' ),
entityForSuggestions.labels.singular_name,
suggestion.name
);
onSelect( {
title,
description: sprintf(
// translators: Represents the description of a user's custom template in the Site Editor, e.g. "Template for Post: Hello, WordPress"
__( 'Template for %1$s' ),
title
),
slug: `single-${ entityForSuggestions.slug }-${ suggestion.slug }`,
} );
} }
>
<span className={ `${ baseCssClass }__title` }>
<TextHighlight text={ suggestion.name } highlight={ search } />
</span>
{ suggestion.link && (
<span className={ `${ baseCssClass }__info` }>
{ suggestion.link }
</span>
) }
</CompositeItem>
);
}

function SuggestionList( { entityForSuggestions, onSelect } ) {
const composite = useCompositeState( { orientation: 'vertical' } );
const [ suggestions, setSuggestions ] = useState( EMPTY_ARRAY );
// We need to track two values, the search input's value(searchInputValue)
// and the one we want to debounce(search) and make REST API requests.
const [ searchInputValue, setSearchInputValue ] = useState( '' );
const [ search, setSearch ] = useState( '' );
const debouncedSearch = useDebounce( setSearch, 250 );
const query = {
...BASE_QUERY,
search,
orderby: search ? 'relevance' : 'modified',
exclude: entityForSuggestions.postsToExclude,
per_page: search ? 20 : 10,
};
const { records: searchResults, hasResolved: searchHasResolved } =
useEntityRecords(
entityForSuggestions.type,
entityForSuggestions.slug,
query
);
useEffect( () => {
if ( search !== searchInputValue ) {
debouncedSearch( searchInputValue );
}
}, [ search, searchInputValue ] );
const entitiesInfo = useMemo( () => {
if ( ! searchResults?.length ) return EMPTY_ARRAY;
return mapToIHasNameAndId( searchResults, 'title.rendered' );
ntsekouras marked this conversation as resolved.
Show resolved Hide resolved
}, [ searchResults ] );
// Update suggestions only when the query has resolved.
useEffect( () => {
if ( ! searchHasResolved ) return;
setSuggestions( entitiesInfo );
}, [ entitiesInfo, searchHasResolved ] );
return (
<>
<SearchControl
onChange={ setSearchInputValue }
value={ searchInputValue }
label={ entityForSuggestions.labels.search_items }
placeholder={ entityForSuggestions.labels.search_items }
/>
{ !! suggestions?.length && (
<Composite
{ ...composite }
role="listbox"
className="edit-site-custom-template-modal__suggestions_list"
>
{ suggestions.map( ( suggestion ) => (
<SuggestionListItem
key={ suggestion.slug }
suggestion={ suggestion }
search={ search }
onSelect={ onSelect }
entityForSuggestions={ entityForSuggestions }
composite={ composite }
/>
) ) }
</Composite>
) }
{ search && ! suggestions?.length && (
<p className="edit-site-custom-template-modal__no-results">
{ entityForSuggestions.labels.not_found }
</p>
) }
</>
);
}

function AddCustomTemplateModal( { onClose, onSelect, entityForSuggestions } ) {
const [ showSearchEntities, setShowSearchEntities ] = useState(
entityForSuggestions.hasGeneralTemplate
);
const baseCssClass = 'edit-site-custom-template-modal';
return (
<Modal
title={ sprintf(
// translators: %s: Name of the post type e.g: "Post".
__( 'Add template: %s' ),
entityForSuggestions.labels.singular_name
) }
className={ baseCssClass }
closeLabel={ __( 'Close' ) }
onRequestClose={ onClose }
>
{ ! showSearchEntities && (
<>
<p>
{ __(
'Select whether to create a single template for all items or a specific one.'
) }
</p>
<Flex
className={ `${ baseCssClass }__contents` }
gap="4"
align="initial"
>
<FlexItem
isBlock
onClick={ () => {
const { slug, title, description } =
entityForSuggestions.template;
onSelect( { slug, title, description } );
} }
>
<Heading level={ 5 }>
{ entityForSuggestions.labels.all_items }
</Heading>
<Text as="span">
{
// translators: The user is given the choice to set up a template for all items of a post type, or just a specific one.
__( 'For all items' )
}
</Text>
</FlexItem>
<FlexItem
isBlock
onClick={ () => {
setShowSearchEntities( true );
} }
>
<Heading level={ 5 }>
{ entityForSuggestions.labels.singular_name }
</Heading>
<Text as="span">
{
// translators: The user is given the choice to set up a template for all items of a post type, or just a specific one.
__( 'For a specific item' )
}
</Text>
</FlexItem>
</Flex>
</>
) }
{ showSearchEntities && (
<>
<p>
{ __(
'This template will be used only for the specific item chosen.'
) }
</p>
<SuggestionList
entityForSuggestions={ entityForSuggestions }
onSelect={ onSelect }
/>
</>
) }
</Modal>
);
}

export default AddCustomTemplateModal;
Loading