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

Edit Site: Add multiple template loading #19141

Merged
merged 36 commits into from
Feb 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a5eb4ec
Template Loader: Don't capitalize template post titles.
epiqueras Dec 13, 2019
5a076af
Templates: Bypass slug deduplication.
epiqueras Dec 13, 2019
b0734e0
Lib: Load all available templates in the site editor.
epiqueras Dec 13, 2019
dc02ce6
Edit Site: Make sure template title is set when publishing.
epiqueras Dec 13, 2019
69ed750
Edit Site: Implement template switcher component.
epiqueras Dec 13, 2019
27230c8
Edit Site: Add a sidebar slot/fill for templates.
epiqueras Dec 13, 2019
00d58b6
Edit Site: Implement template switching.
epiqueras Dec 13, 2019
6d11ca6
Edit Site: Add component for adding templates.
epiqueras Dec 14, 2019
fca9562
Edit Site: Implement template adding in the sidebar.
epiqueras Dec 14, 2019
1ea222d
Edit Site: Change save text from "Customize" to "Update Design".
epiqueras Dec 14, 2019
a74c4aa
Edit Site: Keep track of template part IDs and load them in the site …
epiqueras Dec 15, 2019
16b2d88
Edit Site: Refactor template switcher to be a dropdown menu and allow…
epiqueras Dec 16, 2019
0248569
Edit Site: Move template switcher to the header.
epiqueras Dec 16, 2019
0123540
Edit Site: Show active choice in template switcher.
epiqueras Dec 17, 2019
88d8259
Edit Site: Move template creation to the template switcher.
epiqueras Dec 20, 2019
2abbb67
Edit Site: Switch to new template after creation.
epiqueras Dec 20, 2019
2ec8c5a
Edit Site: Move template switcher to the left.
epiqueras Dec 20, 2019
6c2ed22
Edit Site: Fix header position.
epiqueras Dec 20, 2019
a7db5ee
Edit Site: Fix template creation padding.
epiqueras Dec 20, 2019
b326082
Edit Site: Remove unnecessary class name.
epiqueras Dec 20, 2019
4c824f6
Edit Site: Remove template switcher padding.
epiqueras Dec 20, 2019
fd09ea0
Edit Site: Add theme exporter.
epiqueras Dec 20, 2019
66da25e
Edit Site: Mark customized templates and template parts.
epiqueras Dec 20, 2019
52a2906
Edit Site: Move theme exporter into a header dropdown.
epiqueras Jan 7, 2020
4cf04ba
Edit Site: Rotate more-menu icon.
epiqueras Jan 7, 2020
0f706ac
Edit Site: Update theme exporter label.
epiqueras Jan 7, 2020
97e4846
Edit Site Page: Fix comment typo.
epiqueras Jan 8, 2020
858dc18
Template Loader: Add API for finding the current template for a given…
epiqueras Jan 8, 2020
802909f
Edit Site: Enable link viewer template navigation.
epiqueras Jan 8, 2020
b4500e5
Edit Site: Add block navigation and tool selector.
epiqueras Jan 9, 2020
c6c16e4
Edit Site: Fix template part placeholder preview style conflicts.
epiqueras Jan 10, 2020
c177284
Template Loader: Fix template part post ID reference.
epiqueras Jan 14, 2020
54f012d
Edit Site: Use `useEntityBlockEditor`.
epiqueras Jan 15, 2020
acc17a7
Template Loader: Dedupe resolved templates and template parts.
epiqueras Jan 21, 2020
8d4cbcb
Edit Site: Remove theme exporter.
epiqueras Feb 18, 2020
c78142b
Fix linting and snapshots.
epiqueras Feb 21, 2020
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
3 changes: 3 additions & 0 deletions lib/demo-block-template-parts/header.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<!-- wp:paragraph -->
<p>Header Template Part</p>
<!-- /wp:paragraph -->
1 change: 1 addition & 0 deletions lib/demo-block-templates/front-page.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<!-- wp:template-part {"slug":"header","theme":"twentytwenty"} /-->
Copy link
Member

Choose a reason for hiding this comment

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

Do we need the theme slug here? It'd be good if it could be implicit to the "current theme" as set in options.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's so that customized template parts can persist after switching themes. Different themes could both define headers.

Do you mean to make it default to the current theme if not set? We can do that, but we would need to save it on customized templates so that they don't load a different theme's template parts after a theme switch. It seems like a lot of additional implicit behaviors that might confuse people. I'm not sure saving one attribute in theme files is worth it.

Copy link
Member

Choose a reason for hiding this comment

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

It's a weird requirement to have just for the sake of switching themes. It should be possible to handle it at the time of switching themes as well (also depending on what the UX looks like for what you want to preserve and what you want to replace) since we know both the previous and after contexts. Also not even sure that we want the concept of mixed parts between themes, I'd lean more towards a split of just current theme and user templates.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should be possible to handle it at the time of switching themes as well (also depending on what the UX looks like for what you want to preserve and what you want to replace) since we know both the previous and after contexts.

That is the implicit behavior I mentioned could be confusing.

Also not even sure that we want the concept of mixed parts between themes, I'd lean more towards a split of just current theme and user templates.

It would be confusing if, after switching themes, those user templates started referencing different template parts, hence why we require the attribute after customization.

We could default to the theme that originally created each user template, but then how does the user add new template parts to existing user templates.

Copy link
Member

Choose a reason for hiding this comment

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

I also didn't expect to see the theme slug here, since I interpreted these demo templates as a generic fallback that should work with any given theme.

It's so that customized template parts can persist after switching themes. Different themes could both define headers.

Having said that, I do agree with this part in general. It just didn't seem necessary to me in this particular case. Would it make sense to persist it only once the demo template part has been modified?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What if the user modifies the template, without touching the part, switches themes, and then ends up with a different template part reference. There are too many edge cases like this where we lose with nothing to win.

<!-- wp:columns {"align":"wide"} -->
<div class="wp-block-columns alignwide">
<!-- wp:column -->
Expand Down
53 changes: 50 additions & 3 deletions lib/edit-site-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ class="edit-site"
* @param string $hook Page.
*/
function gutenberg_edit_site_init( $hook ) {
global $_wp_current_template_id;
global
$_wp_current_template_id,
Copy link
Member

Choose a reason for hiding this comment

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

Curious why do we need the globals here, given that these are not coming from core WP, but are just something that's been introduced for site editor (in lib/edit-site-page.php and lib/template-loader.php)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's how we hijack template resolution to resolve block templates.

$_wp_current_template_name,
$_wp_current_template_content,
$_wp_current_template_hierarchy,
$_wp_current_template_part_ids;
if ( 'gutenberg_page_gutenberg-edit-site' !== $hook ) {
return;
}
Expand Down Expand Up @@ -74,11 +79,53 @@ function gutenberg_edit_site_init( $hook ) {
$settings['fontSizes'] = $font_sizes;
}

// Get root template by trigerring `./template-loader.php`'s logic.
// Get all templates by triggering `./template-loader.php`'s logic.
$template_getters = array(
Copy link
Member

Choose a reason for hiding this comment

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

Are we intentionally missing get_index_template here? Also would it make sense to refactor this in order to reuse a similar list defined here:

$template_types = array(
'index',
'404',
'archive',
'author',
'category',
'tag',
'taxonomy',
'date',
// Skip 'embed' for now because it is not a regular template type.
'home',
'frontpage',
'privacypolicy',
'page',
'search',
'single',
'singular',
'attachment',
);

In which case the call below becomes something like:

call_user_func(  'get_' . $template_type . '_template' );

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This needs to match the code from Core template resolution; I think it would be premature to abstract it like that right now.

'get_embed_template',
'get_404_template',
'get_search_template',
'get_home_template',
'get_privacy_policy_template',
'get_post_type_archive_template',
'get_taxonomy_template',
'get_attachment_template',
'get_single_template',
'get_page_template',
'get_singular_template',
'get_category_template',
'get_tag_template',
'get_author_template',
'get_date_template',
'get_archive_template',
);
$template_ids = array();
$template_part_ids = array();
foreach ( $template_getters as $template_getter ) {
call_user_func( $template_getter );
apply_filters( 'template_include', null );
Copy link
Member

Choose a reason for hiding this comment

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

Should we document this filter according to WordPress documentation standards? It might make this logic easier to grok.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But we are not following that convention in the rest of the plugin, are we?

Copy link
Member

Choose a reason for hiding this comment

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

Now that you mention it, I actually don't remember seeing it around.

if ( isset( $_wp_current_template_id ) ) {
$template_ids[ $_wp_current_template_name ] = $_wp_current_template_id;
}
if ( isset( $_wp_current_template_part_ids ) ) {
$template_part_ids = $template_part_ids + $_wp_current_template_part_ids;
}
$_wp_current_template_id = null;
$_wp_current_template_name = null;
$_wp_current_template_content = null;
$_wp_current_template_hierarchy = null;
$_wp_current_template_part_ids = null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be cool to extract all the logic here into a utility function gutenberg_get_available_templates or something like that. this bootstrap is becoming too complex.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea. It might be useful for plugin developers as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Should we do this as a follow-up?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes

get_front_page_template();
get_index_template();
apply_filters( 'template_include', null );
$settings['templateId'] = $_wp_current_template_id;
$template_ids[ $_wp_current_template_name ] = $_wp_current_template_id;
if ( isset( $_wp_current_template_part_ids ) ) {
$template_part_ids = $template_part_ids + $_wp_current_template_part_ids;
}
$settings['templateId'] = $_wp_current_template_id;
$settings['templateType'] = 'wp_template';
$settings['templateIds'] = array_values( $template_ids );
$settings['templatePartIds'] = array_values( $template_part_ids );

// This is so other parts of the code can hook their own settings.
// Example: Global Styles.
Expand Down
67 changes: 48 additions & 19 deletions lib/template-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,24 +81,44 @@ function gutenberg_override_query_template( $template, $type, array $templates =
* @param array $block The root block to start traversing from.
*/
function create_auto_draft_for_template_part_block( $block ) {
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
if ( 'core/template-part' === $block['blockName'] && ! isset( $block['attrs']['id'] ) ) {
$template_part_file_path =
get_stylesheet_directory() . '/block-template-parts/' . $block['attrs']['slug'] . '.html';
if ( ! file_exists( $template_part_file_path ) ) {
return;
global $_wp_current_template_part_ids;

if ( 'core/template-part' === $block['blockName'] ) {
if ( ! isset( $block['attrs']['postId'] ) ) {
$template_part_file_path =
Copy link
Member

Choose a reason for hiding this comment

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

Do you think it would be worthwhile to extract this to get_template_part_file_path( $slug ) helper? It might help with readability here due to nesting, and I imagine we might need this resolution logic elsewhere. It can be abstracted further to include both templates and template parts if needed, since it seems that they follow the same pattern?

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 demo block template code is temporary until we have a proper block-based theme; this will flatten once we can remove it.

get_stylesheet_directory() . '/block-template-parts/' . $block['attrs']['slug'] . '.html';
if ( ! file_exists( $template_part_file_path ) ) {
if ( gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing-demo' ) ) {
$template_part_file_path =
dirname( __FILE__ ) . '/demo-block-template-parts/' . $block['attrs']['slug'] . '.html';
if ( ! file_exists( $template_part_file_path ) ) {
return;
}
} else {
return;
}
}
$template_part_id = wp_insert_post(
array(
'post_content' => file_get_contents( $template_part_file_path ),
'post_title' => $block['attrs']['slug'],
'post_status' => 'auto-draft',
'post_type' => 'wp_template_part',
'post_name' => $block['attrs']['slug'],
'meta_input' => array(
'theme' => $block['attrs']['theme'],
),
)
);
} else {
$template_part_id = $block['attrs']['postId'];
}

if ( isset( $_wp_current_template_part_ids ) ) {
$_wp_current_template_part_ids[ $block['attrs']['slug'] ] = $template_part_id;
} else {
$_wp_current_template_part_ids = array( $block['attrs']['slug'] => $template_part_id );
}
wp_insert_post(
array(
'post_content' => file_get_contents( $template_part_file_path ),
'post_title' => ucfirst( $block['attrs']['slug'] ),
'post_status' => 'auto-draft',
'post_type' => 'wp_template_part',
'post_name' => $block['attrs']['slug'],
'meta_input' => array(
'theme' => $block['attrs']['theme'],
),
)
);
}

foreach ( $block['innerBlocks'] as $inner_block ) {
Expand All @@ -114,7 +134,7 @@ function create_auto_draft_for_template_part_block( $block ) {
* @return string Path to the canvas file to include.
*/
function gutenberg_find_template( $template_file ) {
global $_wp_current_template_id, $_wp_current_template_content, $_wp_current_template_hierarchy;
global $_wp_current_template_id, $_wp_current_template_name, $_wp_current_template_content, $_wp_current_template_hierarchy;

// Bail if no relevant template hierarchy was determined, or if the template file
// was overridden another way.
Expand Down Expand Up @@ -173,7 +193,7 @@ function gutenberg_find_template( $template_file ) {
$post_name = basename( $higher_priority_block_template_path, '.html' );
$current_template_post = array(
'post_content' => file_get_contents( $higher_priority_block_template_path ),
'post_title' => ucfirst( $post_name ),
'post_title' => $post_name,
'post_status' => 'auto-draft',
'post_type' => 'wp_template',
'post_name' => $post_name,
Expand All @@ -192,13 +212,22 @@ function gutenberg_find_template( $template_file ) {
}
}

if ( isset( $_GET['_wp-find-template'] ) ) {
if ( $current_template_post ) {
wp_send_json_success( $current_template_post );
} else {
wp_send_json_error( array( 'message' => __( 'No matching template found.', 'gutenberg' ) ) );
}
}

if ( $current_template_post ) {
if ( is_admin() ) {
foreach ( parse_blocks( $current_template_post->post_content ) as $block ) {
create_auto_draft_for_template_part_block( $block );
}
}
$_wp_current_template_id = $current_template_post->ID;
$_wp_current_template_name = $current_template_post->post_name;
$_wp_current_template_content = empty( $current_template_post->post_content ) ? __( 'Empty template.', 'gutenberg' ) : $current_template_post->post_content;
}

Expand Down
20 changes: 20 additions & 0 deletions lib/templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,26 @@ function gutenberg_grant_template_caps( array $allcaps ) {
}
add_filter( 'user_has_cap', 'gutenberg_grant_template_caps' );

/**
* Filters `wp_template` posts slug resolution to bypass deduplication logic as
* template slugs should be unique.
*
* @param string $slug The resolved slug (post_name).
* @param int $post_ID Post ID.
* @param string $post_status No uniqueness checks are made if the post is still draft or pending.
* @param string $post_type Post type.
* @param int $post_parent Post parent ID.
* @param int $original_slug The desired slug (post_name).
* @return string The original, desired slug.
*/
function gutenberg_filter_wp_template_wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug ) {
if ( 'wp_template' === $post_type ) {
return $original_slug;
}
return $slug;
}
add_filter( 'wp_unique_post_slug', 'gutenberg_filter_wp_template_wp_unique_post_slug', 10, 6 );

/**
* Fixes the label of the 'wp_template' admin menu entry.
*/
Expand Down
55 changes: 42 additions & 13 deletions package-lock.json

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

14 changes: 11 additions & 3 deletions packages/block-editor/src/components/url-popover/link-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { ExternalLink, Button } from '@wordpress/components';
import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url';
import { pencil } from '@wordpress/icons';
import { createSlotFill, ExternalLink, Button } from '@wordpress/components';
import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url';
import { useMemo } from '@wordpress/element';

const { Slot, Fill } = createSlotFill( 'BlockEditorURLPopoverLinkViewer' );

function LinkViewerUrl( { url, urlLabel, className } ) {
const linkClassName = classnames(
Expand All @@ -28,7 +31,7 @@ function LinkViewerUrl( { url, urlLabel, className } ) {
);
}

export default function LinkViewer( {
function LinkViewer( {
className,
linkClassName,
onEditLinkClick,
Expand Down Expand Up @@ -56,6 +59,11 @@ export default function LinkViewer( {
onClick={ onEditLinkClick }
/>
) }
<Slot fillProps={ useMemo( () => ( { url } ), [ url ] ) } />
</div>
);
}

LinkViewer.Fill = Fill;

export default LinkViewer;
Loading