Skip to content
Open
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
3 changes: 3 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions packages/lazy-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Bug Fixes

- Stop resolving a parent template for entities that are never queried content — template parts, patterns, and navigation menus. Falling through the template hierarchy for them resolved an unrelated template, which surfaced as a "Show template" option in the View menu, an "Edit template" command, and that template's styles applying to the canvas ([#81868](https://github.com/WordPress/gutenberg/pull/81868)).
- Add the styles a host passes to the theme's and the user's instead of replacing them, so an editor canvas keeps the CSS a theme registers with `add_editor_style` ([#81747](https://github.com/WordPress/gutenberg/pull/81747)).
- Render the editor preferences modal, so the Preferences menu item and command open it ([#81630](https://github.com/WordPress/gutenberg/pull/81630)).

Expand Down
3 changes: 3 additions & 0 deletions packages/lazy-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
"@wordpress/i18n": "file:../i18n",
"@wordpress/private-apis": "file:../private-apis"
},
"devDependencies": {
"@testing-library/react": "^16.3.2"
},
"peerDependencies": {
"@types/react": "^18 || ^19",
"react": "^18 || ^19"
Expand Down
21 changes: 5 additions & 16 deletions packages/lazy-editor/src/components/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useSelect } from '@wordpress/data';
import { Spinner } from '@wordpress/components';
import { useMemo } from '@wordpress/element';
import { useStylesId } from '../../hooks/use-styles-id';
import { useTemplateId } from '../../hooks/use-template-id';
import { useEditorSettings } from '../../hooks/use-editor-settings';
import { useEditorAssets } from '../../hooks/use-editor-assets';
import { unlock } from '../../lock-unlock';
Expand Down Expand Up @@ -59,22 +60,10 @@ export function Editor( {
const resolvedPostId = postId || homePage?.postId;

// Resolve template ID from post type/ID
const templateId = useSelect(
( select ) => {
if ( ! resolvedPostType || ! resolvedPostId ) {
return undefined;
}
if ( resolvedPostType === 'wp_template' ) {
return resolvedPostId;
}
// Use private API to get template ID for this post
return unlock( select( coreDataStore ) ).getTemplateId(
resolvedPostType,
resolvedPostId
);
},
[ resolvedPostType, resolvedPostId ]
);
const templateId = useTemplateId( {
postType: resolvedPostType,
postId: resolvedPostId,
} );

// Resolve styles ID from template
const stylesId = useStylesId( { templateId } );
Expand Down
114 changes: 114 additions & 0 deletions packages/lazy-editor/src/hooks/test/use-template-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { renderHook } from '@testing-library/react';
import { createRegistry, RegistryProvider } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { useTemplateId } from '../use-template-id';

function createRegistryWithTemplates() {
const registry = createRegistry();
registry.register( coreStore );
const { receiveEntityRecords, addEntities } =
registry.dispatch( coreStore );

// A static front page makes home page resolution synchronous.
receiveEntityRecords( 'root', '__unstableBase', {
show_on_front: 'page',
page_on_front: 2,
} );
addEntities( [
{ kind: 'postType', name: 'wp_template', baseURL: '/wp/v2/templates' },
] );
receiveEntityRecords(
'postType',
'wp_template',
[ { id: 'theme//custom', slug: 'custom' } ],
{ per_page: -1 }
);
return registry;
}

/**
* Seeds an entity that has a template assigned, which is the branch of the
* resolution that does not depend on how the template hierarchy builds its
* fallback slugs. The attachment test below resolves an identically shaped
* entity, so it doubles as proof that these seeds would otherwise resolve.
*
* @param {Object} registry Registry to seed.
* @param {string} postType Post type of the record.
* @param {string|number} postId ID of the record.
*/
function seedEntityWithAssignedTemplate( registry, postType, postId ) {
const { receiveEntityRecords, addEntities } =
registry.dispatch( coreStore );
addEntities( [
{ kind: 'postType', name: postType, baseURL: `/wp/v2/${ postType }` },
] );
receiveEntityRecords( 'postType', postType, [
{ id: postId, template: 'custom' },
] );
}

function renderUseTemplateId( registry, postType, postId ) {
const { result } = renderHook(
() => useTemplateId( { postType, postId } ),
{
wrapper: ( { children } ) => (
<RegistryProvider value={ registry }>
{ children }
</RegistryProvider>
),
}
);
return result.current;
}

describe( 'useTemplateId', () => {
it( 'resolves the assigned template for a post', () => {
const registry = createRegistryWithTemplates();
seedEntityWithAssignedTemplate( registry, 'post', 1 );

expect( renderUseTemplateId( registry, 'post', '1' ) ).toBe(
'theme//custom'
);
} );

it( 'returns the template itself when editing a template', () => {
const registry = createRegistryWithTemplates();

expect(
renderUseTemplateId( registry, 'wp_template', 'theme//single' )
).toBe( 'theme//single' );
} );

it( 'returns undefined when there is no entity to resolve for', () => {
const registry = createRegistryWithTemplates();

expect(
renderUseTemplateId( registry, undefined, undefined )
).toBeUndefined();
} );

it.each( [
[ 'wp_template_part', 'theme//header' ],
[ 'wp_block', 7 ],
[ 'wp_navigation', 8 ],
] )(
'returns undefined for %s, which is never queried content',
( postType, postId ) => {
const registry = createRegistryWithTemplates();
seedEntityWithAssignedTemplate( registry, postType, postId );

expect(
renderUseTemplateId( registry, postType, String( postId ) )
).toBeUndefined();
}
);

it( 'still resolves for an attachment, which has its own place in the template hierarchy', () => {
const registry = createRegistryWithTemplates();
seedEntityWithAssignedTemplate( registry, 'attachment', 9 );

expect( renderUseTemplateId( registry, 'attachment', '9' ) ).toBe(
'theme//custom'
);
} );
} );
61 changes: 61 additions & 0 deletions packages/lazy-editor/src/hooks/use-template-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { store as coreDataStore } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import { unlock } from '../lock-unlock';

/*
* Post types that are never the queried object of a frontend request, so no
* request of theirs runs the template hierarchy and asking which template they
* render inside falls through to an unrelated one — ultimately `index`.
* Template parts and patterns do appear inside templates, but in many of them,
* and never as the thing being queried.
*
* `wp_template` is never queried either, but is answered before this list is
* consulted: the template being edited is its own answer.
*
* The site editor guards the same resolution in `use-resolve-edited-entity`,
* over a longer list that also reflects which entities that editor opens.
*/
const NEVER_QUERIED_POST_TYPES = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That name is a bit weird to me. this is more about the post type not having dedicated frontend pages? what is "queried" about?

Also isn't there already support flags for this, or is the static list mandatory.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah i don't really like the name. the original name was "post types that don't have parent templates" which I also found confusing as they do appear in templates. What about "post types not in template heirarchy"?

'wp_template_part',
'wp_block',
'wp_navigation',
];

/**
* This is a React hook that provides the ID of the template an entity renders
* inside, matching the template WordPress would choose for it on the frontend.
*
* @param props The props object.
* @param props.postType The post type of the edited entity.
* @param props.postId The ID of the edited entity.
* @return The template ID, or `undefined` when there is no template to resolve.
*/
export function useTemplateId( {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Does this actually belong in core data?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm pretty sure we have versions of this hook in a lot of places. And yes, maybe core-data is the right place for it.

postType,
postId,
}: {
postType?: string;
postId?: string;
} = {} ) {
return useSelect(
( select ) => {
if ( ! postType || ! postId ) {
return undefined;
}

if ( postType === 'wp_template' ) {
return postId;
}

if ( NEVER_QUERIED_POST_TYPES.includes( postType ) ) {
return undefined;
}

return unlock( select( coreDataStore ) ).getTemplateId(
postType,
postId
);
},
[ postType, postId ]
);
}
Loading