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

Quick Edit: add Template field #66591

Merged
merged 50 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
da0b766
Add DataFormProvider for fields
louwie17 Oct 4, 2024
538ef3e
Add dataform layout component and inline layout
louwie17 Oct 28, 2024
9cb2b26
Fix label in panel view
louwie17 Oct 28, 2024
c641723
Remove unneeded line
louwie17 Oct 28, 2024
a707275
Update `field` to FormField as well
louwie17 Oct 28, 2024
111e9f1
Merge branch 'update/dataform_combined_fields_api' of github.com:Word…
gigitux Oct 29, 2024
93b8e53
QuickEdit: implement Template field
gigitux Oct 30, 2024
9806534
Merge branch 'trunk' of github.com:WordPress/gutenberg into add/templ…
gigitux Oct 30, 2024
ca3b59b
improve style
gigitux Oct 30, 2024
61f9655
implement logic to reset to default template
gigitux Oct 30, 2024
5a623fb
ensure that setting object is defined
gigitux Oct 30, 2024
f9b1eef
Add DataFormProvider for fields
louwie17 Oct 4, 2024
db4cbd6
Add dataform layout component and inline layout
louwie17 Oct 28, 2024
2d7ee1b
Fix label in panel view
louwie17 Oct 28, 2024
2ba1ffb
Remove unneeded line
louwie17 Oct 28, 2024
4384814
Update `field` to FormField as well
louwie17 Oct 28, 2024
8e4b0ef
Remove combinedFields usage
louwie17 Oct 31, 2024
152c875
Remove old use of View
louwie17 Oct 31, 2024
7cfb87d
Add label and move field type check to 'getFieldDefinition'
louwie17 Oct 31, 2024
a3768d8
Create types of each view
louwie17 Oct 31, 2024
3e5a584
Add sticky example
louwie17 Oct 31, 2024
58150ef
Update combined fields story
louwie17 Oct 31, 2024
b523f7f
Merge branch 'update/dataform_combined_fields_api' of github.com:Word…
gigitux Oct 31, 2024
8698f0f
Add DataFormProvider for fields
louwie17 Oct 4, 2024
6a0598d
Add dataform layout component and inline layout
louwie17 Oct 28, 2024
bbae788
Fix label in panel view
louwie17 Oct 28, 2024
e17c228
Remove unneeded line
louwie17 Oct 28, 2024
05d7506
Update `field` to FormField as well
louwie17 Oct 28, 2024
15b0f11
Remove combinedFields usage
louwie17 Oct 31, 2024
a8d2462
Remove old use of View
louwie17 Oct 31, 2024
5967797
Add label and move field type check to 'getFieldDefinition'
louwie17 Oct 31, 2024
7eac0ab
Create types of each view
louwie17 Oct 31, 2024
a8f9e82
Add sticky example
louwie17 Oct 31, 2024
f9e86cb
Update combined fields story
louwie17 Oct 31, 2024
b05dbef
Fix change I missed during rebase
louwie17 Oct 31, 2024
8c15a9b
Remove old status_and_visibility field
louwie17 Nov 4, 2024
8ea852c
Rename fields to children for clarity
louwie17 Nov 4, 2024
0c67f64
Add children support to regular layout
louwie17 Nov 4, 2024
4cd314b
Replace inline with labelPosition
louwie17 Nov 5, 2024
ddf996a
Remove field type checking within dataform layouts
louwie17 Nov 5, 2024
f31a91c
Merge branch 'update/dataform_combined_fields_api' of github.com:Word…
gigitux Nov 6, 2024
9021f79
Merge branch 'trunk' of github.com:WordPress/gutenberg into add/templ…
gigitux Nov 25, 2024
d698e14
fix template field registration
gigitux Nov 25, 2024
69396c0
restore not necessary changes
gigitux Nov 25, 2024
7864b00
Merge branch 'trunk' of github.com:WordPress/gutenberg into add/templ…
gigitux Dec 2, 2024
704ebe5
wrap DataForm component
gigitux Dec 2, 2024
7cf2843
not use hook
gigitux Dec 2, 2024
cbb8bda
improve dependency
gigitux Dec 3, 2024
f891475
improve code style
gigitux Dec 3, 2024
32e1b31
add issue
gigitux Dec 3, 2024
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
1 change: 1 addition & 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/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ $z-layers: (
".editor-action-modal": 1000001,
".editor-post-template__swap-template-modal": 1000001,
".edit-site-template-panel__replace-template-modal": 1000001,
".fields-controls__template-modal": 1000001,

// Note: The ConfirmDialog component's z-index is being set to 1000001 in packages/components/src/confirm-dialog/styles.ts
// because it uses emotion and not sass. We need it to render on top its parent popover.
Expand Down
35 changes: 34 additions & 1 deletion packages/edit-site/src/components/post-edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { privateApis as editorPrivateApis } from '@wordpress/editor';
*/
import Page from '../page';
import { unlock } from '../../lock-unlock';
import usePatternSettings from '../page-patterns/use-pattern-settings';
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';

const { PostCardPanel, usePostFields } = unlock( editorPrivateApis );

Expand Down Expand Up @@ -85,6 +87,12 @@ function PostEditForm( { postType, postId } ) {
'slug',
'parent',
'comment_status',
{
label: __( 'Template' ),
labelPosition: 'side',
id: 'template',
layout: 'regular',
},
].filter(
( field ) =>
ids.length === 1 ||
Expand Down Expand Up @@ -123,14 +131,39 @@ function PostEditForm( { postType, postId } ) {
setMultiEdits( {} );
}, [ ids ] );

const { ExperimentalBlockEditorProvider } = unlock(
blockEditorPrivateApis
);
const settings = usePatternSettings();

/**
* The template field depends on the block editor settings.
* This is a workaround to ensure that the block editor settings are available.
*/
const fieldsWithDependency = useMemo( () => {
return fields.map( ( field ) => {
if ( field.id === 'template' ) {
return {
...field,
Edit: ( data ) => (
<ExperimentalBlockEditorProvider settings={ settings }>
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this here and not directly in the template field edit?

Copy link
Contributor

Choose a reason for hiding this comment

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

Not asking to change it, just curious why

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 need to pass settings to this provider and usePatternSettings depends on the edit site store:

export default function usePatternSettings() {
const storedSettings = useSelect( ( select ) => {
const { getSettings } = unlock( select( editSiteStore ) );
return getSettings();
}, [] );

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, here are my thoughts on this. Not necessarily something to be done here though

We need to check what settings exactly are needed for these previews. It seems mostly about the list of available patterns (maybe there's other stuff but I'm not sure). IMO, these could be provided directly from core-data package selectors... without the "settings" object. This is a cleanup that needs to be done at some point. I mention some of this here https://make.wordpress.org/core/2024/09/12/gutenberg-development-practices-and-common-pitfalls/ (It's also kind of a similar change to what we did for defaultTemplates and template parts)

I'm ok shipping this as is now but I feel like if someone uses the "template" field, it should just work without any hidden requirement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I agree with you! I'm happy to:

  • open an issue.
  • add the issue link on this comment.
  • dedicate some time in the upcoming weeks to fix this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Awesome ❤️

<field.Edit { ...data } />
</ExperimentalBlockEditorProvider>
),
};
}
return field;
} );
}, [ fields, settings ] );

return (
<VStack spacing={ 4 }>
{ ids.length === 1 && (
<PostCardPanel postType={ postType } postId={ ids[ 0 ] } />
) }
<DataForm
data={ ids.length === 1 ? record : multiEdits }
fields={ fields }
fields={ fieldsWithDependency }
form={ form }
onChange={ onChange }
/>
Expand Down
2 changes: 2 additions & 0 deletions packages/editor/src/dataviews/store/private-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
statusField,
authorField,
titleField,
templateField,
} from '@wordpress/fields';
import duplicateTemplatePart from '../actions/duplicate-template-part';

Expand Down Expand Up @@ -171,6 +172,7 @@ export const registerPostTypeSchema =
postTypeConfig.supports?.[ 'page-attributes' ] && parentField,
postTypeConfig.supports?.comments && commentStatusField,
passwordField,
templateField,
].filter( Boolean );

registry.batch( () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/fields/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ Undocumented declaration.

Status field for BasePost.

### templateField

Undocumented declaration.
Copy link
Member

Choose a reason for hiding this comment

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

We should aim to document every export. It's quite discouraging to look at docs that say "Undocumented declaration".


### titleField

Undocumented declaration.
Expand Down
1 change: 1 addition & 0 deletions packages/fields/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@babel/runtime": "7.25.7",
"@wordpress/api-fetch": "*",
"@wordpress/blob": "*",
"@wordpress/block-editor": "*",
"@wordpress/blocks": "*",
"@wordpress/components": "*",
"@wordpress/compose": "*",
Expand Down
4 changes: 3 additions & 1 deletion packages/fields/src/actions/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export function isTemplateOrTemplatePart(
return p.type === TEMPLATE_POST_TYPE || p.type === TEMPLATE_PART_POST_TYPE;
}

export function getItemTitle( item: Post ): string {
export function getItemTitle( item: {
title: string | { rendered: string } | { raw: string };
} ) {
if ( typeof item.title === 'string' ) {
return decodeEntities( item.title );
}
Expand Down
1 change: 1 addition & 0 deletions packages/fields/src/fields/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { default as slugField } from './slug';
export { default as titleField } from './title';
export { default as orderField } from './order';
export { default as featuredImageField } from './featured-image';
export { default as templateField } from './template';
export { default as parentField } from './parent';
export { default as passwordField } from './password';
export { default as statusField } from './status';
Expand Down
22 changes: 22 additions & 0 deletions packages/fields/src/fields/template/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* WordPress dependencies
*/
import type { Field } from '@wordpress/dataviews';

/**
* Internal dependencies
*/
import { __ } from '@wordpress/i18n';
import type { BasePost } from '../../types';
import { TemplateEdit } from './template-edit';

const templateField: Field< BasePost > = {
id: 'template',
type: 'text',
label: __( 'Template' ),
getValue: ( { item } ) => item.template,
Copy link
Member

Choose a reason for hiding this comment

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

We don't need to provide a getValue function here given id is template.

Edit: TemplateEdit,
enableSorting: false,
};

export default templateField;
23 changes: 23 additions & 0 deletions packages/fields/src/fields/template/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.fields-controls__template-modal {
z-index: z-index(".fields-controls__template-modal");
}

.fields-controls__template-content .block-editor-block-patterns-list {
column-count: 2;
column-gap: $grid-unit-30;

// Small top padding required to avoid cutting off the visible outline when hovering items
padding-top: $border-width-focus-fallback;

@include break-medium() {
column-count: 3;
}

@include break-wide() {
column-count: 4;
}

.block-editor-block-patterns-list__list-item {
break-inside: avoid-column;
}
}
210 changes: 210 additions & 0 deletions packages/fields/src/fields/template/template-edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/**
Copy link
Member

Choose a reason for hiding this comment

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

For file names: we could simplify to edit or view if they are within a field folder.

* WordPress dependencies
*/
import { useCallback, useMemo, useState } from '@wordpress/element';
// @ts-ignore
import { parse } from '@wordpress/blocks';
import type { WpTemplate } from '@wordpress/core-data';
import { store as coreStore } from '@wordpress/core-data';
import type { DataFormControlProps } from '@wordpress/dataviews';

/**
* Internal dependencies
*/
// @ts-expect-error block-editor is not typed correctly.
import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor';
import {
Button,
Dropdown,
MenuGroup,
MenuItem,
Modal,
} from '@wordpress/components';
import { useAsyncList } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
import { decodeEntities } from '@wordpress/html-entities';
import { __ } from '@wordpress/i18n';
import { getItemTitle } from '../../actions/utils';
import type { BasePost } from '../../types';
import { unlock } from '../../lock-unlock';

export const TemplateEdit = ( {
data,
field,
onChange,
}: DataFormControlProps< BasePost > ) => {
const { id } = field;
const postType = data.type;
const postId =
typeof data.id === 'number' ? data.id : parseInt( data.id, 10 );
const slug = data.slug;

const { availableTemplates, templates } = useSelect(
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not merge both useSelect calls?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed with f891475

( select ) => {
const allTemplates =
select( coreStore ).getEntityRecords< WpTemplate >(
'postType',
'wp_template',
{
per_page: -1,
post_type: postType,
}
) ?? [];

const { getHomePage, getPostsPageId } = unlock(
select( coreStore )
);

const isPostsPage = getPostsPageId() === +postId;
const isFrontPage =
postType === 'page' && getHomePage()?.postId === +postId;

const allowSwitchingTemplate = ! isPostsPage && ! isFrontPage;

return {
templates: allTemplates,
availableTemplates: allowSwitchingTemplate
? allTemplates.filter(
( template ) =>
template.is_custom &&
template.slug !== data.template &&
!! template.content.raw // Skip empty templates.
)
: [],
};
},
[ data.template, postId, postType ]
);

const templatesAsPatterns = useMemo(
() =>
availableTemplates.map( ( template ) => ( {
name: template.slug,
blocks: parse( template.content.raw ),
title: decodeEntities( template.title.rendered ),
id: template.id,
} ) ),
[ availableTemplates ]
);

const shownTemplates = useAsyncList( templatesAsPatterns );

const value = field.getValue( { item: data } );

const currentTemplate = useSelect(
( select ) => {
const foundTemplate = templates?.find(
( template ) => template.slug === value
);

if ( foundTemplate ) {
return foundTemplate;
}

let slugToCheck;
// In `draft` status we might not have a slug available, so we use the `single`
// post type templates slug(ex page, single-post, single-product etc..).
// Pages do not need the `single` prefix in the slug to be prioritized
// through template hierarchy.
if ( slug ) {
slugToCheck =
postType === 'page'
? `${ postType }-${ slug }`
: `single-${ postType }-${ slug }`;
} else {
slugToCheck =
postType === 'page' ? 'page' : `single-${ postType }`;
}

if ( postType ) {
const templateId = select( coreStore ).getDefaultTemplateId( {
slug: slugToCheck,
} );

return select( coreStore ).getEntityRecord(
'postType',
'wp_template',
templateId
);
}
},
[ postType, slug, templates, value ]
);

const [ showModal, setShowModal ] = useState( false );

const onChangeControl = useCallback(
( newValue: string ) =>
onChange( {
[ id ]: newValue,
} ),
[ id, onChange ]
);

return (
<fieldset className="fields-controls__template">
<Dropdown
popoverProps={ { placement: 'bottom-start' } }
renderToggle={ ( { onToggle } ) => (
<Button
__next40pxDefaultSize
variant="tertiary"
size="compact"
onClick={ onToggle }
>
{ currentTemplate
? getItemTitle( currentTemplate )
: '' }
</Button>
) }
renderContent={ ( { onToggle } ) => (
<MenuGroup>
<MenuItem
onClick={ () => {
setShowModal( true );
onToggle();
} }
>
{ __( 'Swap template' ) }
</MenuItem>
{
// The default template in a post is indicated by an empty string
value !== '' && (
<MenuItem
onClick={ () => {
onChangeControl( '' );
onToggle();
} }
>
{ __( 'Use default template' ) }
</MenuItem>
)
}
</MenuGroup>
) }
/>
{ showModal && (
<Modal
title={ __( 'Choose a template' ) }
onRequestClose={ () => setShowModal( false ) }
overlayClassName="fields-controls__template-modal"
isFullScreen
>
<div className="fields-controls__template-content">
<BlockPatternsList
label={ __( 'Templates' ) }
blockPatterns={ templatesAsPatterns }
shownPatterns={ shownTemplates }
onClickPattern={ (
template: ( typeof templatesAsPatterns )[ 0 ]
) => {
onChangeControl( template.name );
setShowModal( false );
} }
/>
</div>
</Modal>
) }
</fieldset>
);
};
1 change: 1 addition & 0 deletions packages/fields/src/style.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@import "./fields/slug/style.scss";
@import "./fields/featured-image/style.scss";
@import "./fields/template/style.scss";
Loading
Loading