-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Changes from 49 commits
da0b766
538ef3e
9cb2b26
c641723
a707275
111e9f1
93b8e53
9806534
ca3b59b
61f9655
5a623fb
f9b1eef
db4cbd6
2d7ee1b
2ba1ffb
4384814
8e4b0ef
152c875
7cfb87d
a3768d8
3e5a584
58150ef
b523f7f
8698f0f
6a0598d
bbae788
e17c228
05d7506
15b0f11
a8d2462
5967797
7eac0ab
a8f9e82
f9e86cb
b05dbef
8c15a9b
8ea852c
0c67f64
4cd314b
ddf996a
f31a91c
9021f79
d698e14
69396c0
7864b00
704ebe5
7cf2843
cbb8bda
f891475
32e1b31
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -98,6 +98,10 @@ Undocumented declaration. | |
|
||
Status field for BasePost. | ||
|
||
### templateField | ||
|
||
Undocumented declaration. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to provide a |
||
Edit: TemplateEdit, | ||
enableSorting: false, | ||
}; | ||
|
||
export default templateField; |
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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
/** | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For file names: we could simplify to |
||
* 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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not merge both useSelect calls? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
); | ||
}; |
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"; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 andusePatternSettings
depends on the edit site store:gutenberg/packages/edit-site/src/components/page-patterns/use-pattern-settings.js
Lines 15 to 20 in f891475
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome ❤️