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

Turn Template settings panel into a Template popover #41925

Merged
merged 16 commits into from
Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 packages/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ $z-layers: (
// Show tooltips above NUX tips, wp-admin menus, submenus, and sidebar:
".components-tooltip": 1000002,

// Keep template popover underneath 'Create custom template' modal overlay.
".edit-post-post-template__dialog": 99999,

// Make sure corner handles are above side handles for ResizableBox component
".components-resizable-box__handle": 2,
".components-resizable-box__side-handle": 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import PostFormat from '../post-format';
import PostPendingStatus from '../post-pending-status';
import PluginPostStatusInfo from '../plugin-post-status-info';
import { store as editPostStore } from '../../../store';
import PostTemplate from '../post-template';

/**
* Module Constants
Expand All @@ -29,7 +30,7 @@ function PostStatus( { isOpened, onTogglePanel } ) {
return (
<PanelBody
className="edit-post-post-status"
title={ __( 'Status & visibility' ) }
title={ __( 'Summary' ) }
opened={ isOpened }
onToggle={ onTogglePanel }
>
Expand All @@ -38,6 +39,7 @@ function PostStatus( { isOpened, onTogglePanel } ) {
<>
<PostVisibility />
<PostSchedule />
<PostTemplate />
<PostFormat />
<PostSticky />
<PostPendingStatus />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* External dependencies
*/
import { kebabCase } from 'lodash';

/**
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { useState } from '@wordpress/element';
import { serialize, createBlock } from '@wordpress/blocks';
import {
Modal,
Flex,
FlexItem,
TextControl,
Button,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { store as editPostStore } from '../../../store';

const DEFAULT_TITLE = __( 'Custom Template' );

export default function PostTemplateCreateModal( { onClose } ) {
const settings = useSelect(
( select ) => select( editorStore ).getEditorSettings(),
[]
);
noisysocks marked this conversation as resolved.
Show resolved Hide resolved
noisysocks marked this conversation as resolved.
Show resolved Hide resolved

const { __unstableCreateTemplate, __unstableSwitchToTemplateMode } =
useDispatch( editPostStore );

const [ title, setTitle ] = useState( '' );

const [ isBusy, setIsBusy ] = useState( false );

const cancel = () => {
setTitle( '' );
onClose();
};

const submit = async ( event ) => {
event.preventDefault();

if ( isBusy ) {
return;
}

setIsBusy( true );

const newTemplateContent =
settings.defaultBlockTemplate ??
serialize( [
createBlock(
'core/group',
{
tagName: 'header',
layout: { inherit: true },
},
[
createBlock( 'core/site-title' ),
createBlock( 'core/site-tagline' ),
]
),
createBlock( 'core/separator' ),
createBlock(
'core/group',
{
tagName: 'main',
},
[
createBlock(
'core/group',
{
layout: { inherit: true },
},
[ createBlock( 'core/post-title' ) ]
),
createBlock( 'core/post-content', {
layout: { inherit: true },
} ),
]
),
] );

await __unstableCreateTemplate( {
slug: 'wp-custom-template-' + kebabCase( title || DEFAULT_TITLE ),
noisysocks marked this conversation as resolved.
Show resolved Hide resolved
content: newTemplateContent,
title: title || DEFAULT_TITLE,
} );

setIsBusy( false );
cancel();

__unstableSwitchToTemplateMode( true );
};

return (
<Modal
title={ __( 'Create custom template' ) }
closeLabel={ __( 'Close' ) }
onRequestClose={ cancel }
overlayClassName="edit-post-post-template__modal"
>
<form onSubmit={ submit }>
<Flex align="flex-start" gap={ 8 }>
<FlexItem>
<TextControl
label={ __( 'Name' ) }
value={ title }
onChange={ setTitle }
placeholder={ DEFAULT_TITLE }
disabled={ isBusy }
help={ __(
'Describe the purpose of the template, e.g. "Full Width". Custom templates can be applied to any post or page.'
) }
/>
</FlexItem>
</Flex>

<Flex
className="edit-post-template__modal-actions"
justify="flex-end"
expanded={ false }
>
<FlexItem>
<Button variant="tertiary" onClick={ cancel }>
{ __( 'Cancel' ) }
</Button>
</FlexItem>
<FlexItem>
<Button
variant="primary"
type="submit"
isBusy={ isBusy }
aria-disabled={ isBusy }
>
{ __( 'Create' ) }
</Button>
</FlexItem>
</Flex>
</form>
</Modal>
);
}
78 changes: 78 additions & 0 deletions packages/edit-post/src/components/sidebar/post-template/form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
import { __experimentalInspectorPopoverHeader as InspectorPopoverHeader } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
import { addCard } from '@wordpress/icons';
import { Notice, SelectControl, Button } from '@wordpress/components';

/**
* Internal dependencies
*/
import PostTemplateCreateModal from './create-modal';

export default function PostTemplateForm( {
isPostsPage,
selectedOption,
options,
canCreate,
canEdit,
onChange,
onEdit,
onClose,
} ) {
const [ isCreateModalOpen, setIsCreateModalOpen ] = useState( false );

return (
<>
<InspectorPopoverHeader
noisysocks marked this conversation as resolved.
Show resolved Hide resolved
title={ __( 'Template' ) }
help={ __(
'Templates define the way your content is displayed when viewing your site.'
noisysocks marked this conversation as resolved.
Show resolved Hide resolved
) }
actions={
canCreate
? [
{
icon: addCard,
label: __( 'Add template' ),
onClick: () => setIsCreateModalOpen( true ),
},
]
: []
}
onClose={ onClose }
/>
{ isPostsPage ? (
<Notice
className="edit-post-post-template__notice"
status="warning"
isDismissible={ false }
>
{ __( 'The posts page template cannot be changed.' ) }
</Notice>
Comment on lines +104 to +111
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't work because of the missing backport (#38607 (comment)), but you can use the following snippet for testing.

add_action( 'init', function() {
	register_setting(
		'reading',
		'page_for_posts',
		array(
			'show_in_rest' => true,
			'type'         => 'number',
			'description'  => __( 'The ID of the page that should display the latest posts', 'gutenberg' ),
		)
	);
} );

Copy link
Member Author

@noisysocks noisysocks Jun 27, 2022

Choose a reason for hiding this comment

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

Thanks! I was wondering what this was.

) : (
<SelectControl
hideLabelFromVision
label={ __( 'Template' ) }
value={ selectedOption?.value ?? '' }
options={ options }
onChange={ onChange }
/>
) }
{ canEdit && (
<p>
<Button variant="link" onClick={ onEdit }>
{ __( 'Edit template' ) }
</Button>
</p>
) }
{ isCreateModalOpen && (
<PostTemplateCreateModal
onClose={ () => setIsCreateModalOpen( false ) }
/>
) }
</>
);
}
110 changes: 110 additions & 0 deletions packages/edit-post/src/components/sidebar/post-template/hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { store as coreStore } from '@wordpress/core-data';
import { useMemo, useCallback } from '@wordpress/element';

/**
* Internal dependencies
*/
import { store as editPostStore } from '../../../store';

export default function usePostTemplateForm() {
noisysocks marked this conversation as resolved.
Show resolved Hide resolved
const isViewable = useSelect( ( select ) => {
const { getCurrentPostType } = select( editorStore );
const { getPostType } = select( coreStore );
return getPostType( getCurrentPostType() )?.viewable ?? false;
}, [] );

const settings = useSelect(
( select ) => select( editorStore ).getEditorSettings(),
[]
);
noisysocks marked this conversation as resolved.
Show resolved Hide resolved

const canUserCreateTemplate = useSelect(
( select ) => select( coreStore ).canUser( 'create', 'templates' ),
[]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be overkill for this, but there's a useResourcePermissions hook that makes this easier - #38785

That also tells you if the entity can be edited when an id is provided. I notice you're using the 'create' permission for that at the moment. I don't know if there's any difference.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good tip about useResourcePermissions. I think it's overkill here while we're only checking create.

I didn't want to change any of the PostTemplatePanel logic as part of this PR. We only check create in trunk so that's what I'm doing here. I'm not sure if we should check edit. Probably a good idea to investigate that separately.


const currentPostId = useSelect(
( select ) => select( editorStore ).getCurrentPostId(),
[]
);

const templates = useSelect( ( select ) => {
const { getCurrentPostType } = select( editorStore );
const { getEntityRecords } = select( coreStore );
return getEntityRecords( 'postType', 'wp_template', {
post_type: getCurrentPostType(),
per_page: -1,
} );
}, [] );

const templateAttribute = useSelect(
( select ) =>
select( editorStore ).getEditedPostAttribute( 'template' ),
[]
);

const template = useSelect(
( select ) => select( editPostStore ).getEditedPostTemplate(),
[]
);

const { editPost } = useDispatch( editorStore );

const { __unstableSwitchToTemplateMode } = useDispatch( editPostStore );

const isVisible =
isViewable &&
( settings.availableTemplates?.length ||
( settings.supportsTemplateMode && canUserCreateTemplate ) );

const isPostsPage = currentPostId === settings?.page_for_posts;
noisysocks marked this conversation as resolved.
Show resolved Hide resolved

const options = useMemo(
() =>
Object.entries( {
...settings.availableTemplates,
...Object.fromEntries(
( templates ?? [] ).map( ( { slug, title } ) => [
slug,
title.rendered,
] )
),
} ).map( ( [ slug, title ] ) => ( { value: slug, label: title } ) ),
[ settings.availableTemplates, templates ]
);

const selectedOption =
options.find( ( option ) => option.value === templateAttribute ) ??
options.find( ( option ) => ! option.value ); // The default option has '' value.

const canCreate = canUserCreateTemplate && ! isPostsPage;

const canEdit =
canUserCreateTemplate && settings.supportsTemplateMode && !! template;

const onChange = useCallback( ( slug ) => {
editPost( {
template: slug || '',
} );
}, [] );

const onEdit = useCallback( () => {
__unstableSwitchToTemplateMode();
}, [] );
noisysocks marked this conversation as resolved.
Show resolved Hide resolved

return {
isVisible,
isPostsPage,
selectedOption,
options,
canCreate,
canEdit,
onChange,
onEdit,
};
}
Loading