diff --git a/docs/reference-guides/slotfills/README.md b/docs/reference-guides/slotfills/README.md index 5be4de892073d..b3ab4dcc08386 100644 --- a/docs/reference-guides/slotfills/README.md +++ b/docs/reference-guides/slotfills/README.md @@ -37,7 +37,7 @@ SlotFills are created using `createSlotFill`. This creates two components, `Slot ```js /** - * Defines as extensibility slot for the Status & visibility panel. + * Defines as extensibility slot for the Summary panel. */ /** @@ -58,7 +58,7 @@ PluginPostStatusInfo.Slot = Slot; export default PluginPostStatusInfo; ``` -This new Slot is then exposed in the editor. The example below is from core and represents the Status & visibility panel. +This new Slot is then exposed in the editor. The example below is from core and represents the Summary panel. As we can see, the `` is wrapping all of the items that will appear in the panel. Any items that have been added via the SlotFill ( see the example above ), will be included in the `fills` parameter and be displayed between the `` and `` components. @@ -69,7 +69,7 @@ See [core code](https://github.com/WordPress/gutenberg/tree/HEAD/packages/edit-p const PostStatus = ( { isOpened, onTogglePanel } ) => ( diff --git a/docs/reference-guides/slotfills/plugin-post-status-info.md b/docs/reference-guides/slotfills/plugin-post-status-info.md index ce45f981c6b93..2fc0d50050ee7 100644 --- a/docs/reference-guides/slotfills/plugin-post-status-info.md +++ b/docs/reference-guides/slotfills/plugin-post-status-info.md @@ -1,6 +1,6 @@ # PluginPostStatusInfo -This slots allows for the insertion of items in the Status & visibility panel of the document sidebar. +This slots allows for the insertion of items in the Summary panel of the document sidebar. ## Example @@ -19,4 +19,4 @@ registerPlugin( 'post-status-info-test', { render: PluginPostStatusInfoTest } ); ## Location -![Location in the Status & visibility panel](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/plugin-post-status-info-location.png?raw=true) +![Location in the Summary panel](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/plugin-post-status-info-location.png?raw=true) diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index e425d111e49f1..07c4b1d2ba175 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -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, diff --git a/packages/e2e-tests/specs/editor/plugins/iframed-block.test.js b/packages/e2e-tests/specs/editor/plugins/iframed-block.test.js index b7a3bc7f252ea..964cb15ceaaad 100644 --- a/packages/e2e-tests/specs/editor/plugins/iframed-block.test.js +++ b/packages/e2e-tests/specs/editor/plugins/iframed-block.test.js @@ -36,8 +36,9 @@ describe( 'changing image size', () => { await openDocumentSettingsSidebar(); await clickButton( 'Page' ); - await clickButton( 'Template' ); - await clickButton( 'New' ); + await page.click( 'button[aria-label^="Select template"]' ); + await page.waitForSelector( 'button[aria-label="Add template"]' ); + await page.click( 'button[aria-label="Add template"]' ); await page.keyboard.press( 'Tab' ); await page.keyboard.press( 'Tab' ); await page.keyboard.type( 'Iframed Test' ); diff --git a/packages/e2e-tests/specs/editor/plugins/iframed-masonry-block.test.js b/packages/e2e-tests/specs/editor/plugins/iframed-masonry-block.test.js index 7877442e5695e..67859242124ac 100644 --- a/packages/e2e-tests/specs/editor/plugins/iframed-masonry-block.test.js +++ b/packages/e2e-tests/specs/editor/plugins/iframed-masonry-block.test.js @@ -44,8 +44,9 @@ describe( 'iframed masonry block', () => { await openDocumentSettingsSidebar(); await clickButton( 'Page' ); - await clickButton( 'Template' ); - await clickButton( 'New' ); + await page.click( 'button[aria-label^="Select template"]' ); + await page.waitForSelector( 'button[aria-label="Add template"]' ); + await page.click( 'button[aria-label="Add template"]' ); await page.keyboard.press( 'Tab' ); await page.keyboard.press( 'Tab' ); await page.keyboard.type( 'Iframed Test' ); diff --git a/packages/e2e-tests/specs/editor/various/sidebar.test.js b/packages/e2e-tests/specs/editor/various/sidebar.test.js index 2be81eac943e7..39409b0fbeb7b 100644 --- a/packages/e2e-tests/specs/editor/various/sidebar.test.js +++ b/packages/e2e-tests/specs/editor/various/sidebar.test.js @@ -131,9 +131,7 @@ describe( 'Sidebar', () => { ).toBeDefined(); expect( await findSidebarPanelWithTitle( 'Excerpt' ) ).toBeDefined(); expect( await findSidebarPanelWithTitle( 'Discussion' ) ).toBeDefined(); - expect( - await findSidebarPanelWithTitle( 'Status & visibility' ) - ).toBeDefined(); + expect( await findSidebarPanelWithTitle( 'Summary' ) ).toBeDefined(); await page.evaluate( () => { const { removeEditorPanel } = wp.data.dispatch( 'core/edit-post' ); @@ -165,8 +163,8 @@ describe( 'Sidebar', () => { expect( await page.$x( getPanelToggleSelector( 'Discussion' ) ) ).toEqual( [] ); - expect( - await page.$x( getPanelToggleSelector( 'Status & visibility' ) ) - ).toEqual( [] ); + expect( await page.$x( getPanelToggleSelector( 'Summary' ) ) ).toEqual( + [] + ); } ); } ); diff --git a/packages/edit-post/README.md b/packages/edit-post/README.md index add8d802ed496..048db51d88cee 100644 --- a/packages/edit-post/README.md +++ b/packages/edit-post/README.md @@ -268,7 +268,7 @@ _Returns_ ### PluginPostStatusInfo -Renders a row in the Status & visibility panel of the Document sidebar. +Renders a row in the Summary panel of the Document sidebar. It should be noted that this is named and implemented around the function it serves and not its location, which may change in future iterations. diff --git a/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js b/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js index 3afc6b2c59956..0e79742f3048d 100644 --- a/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js @@ -1,5 +1,5 @@ /** - * Defines as extensibility slot for the Status & visibility panel. + * Defines as extensibility slot for the Summary panel. */ /** @@ -10,7 +10,7 @@ import { createSlotFill, PanelRow } from '@wordpress/components'; export const { Fill, Slot } = createSlotFill( 'PluginPostStatusInfo' ); /** - * Renders a row in the Status & visibility panel of the Document sidebar. + * Renders a row in the Summary panel of the Document sidebar. * It should be noted that this is named and implemented around the function it serves * and not its location, which may change in future iterations. * diff --git a/packages/edit-post/src/components/sidebar/post-schedule/style.scss b/packages/edit-post/src/components/sidebar/post-schedule/style.scss index 78fe19c77bece..39e049bfbeed2 100644 --- a/packages/edit-post/src/components/sidebar/post-schedule/style.scss +++ b/packages/edit-post/src/components/sidebar/post-schedule/style.scss @@ -28,6 +28,6 @@ } } -.edit-post-post-schedule__dialog .components-popover__content { - padding: $grid-unit-20; +.edit-post-post-schedule__dialog .block-editor-publish-date-time-picker { + margin: $grid-unit-10; } diff --git a/packages/edit-post/src/components/sidebar/post-status/index.js b/packages/edit-post/src/components/sidebar/post-status/index.js index 1d6ba9131a9c8..b0ffe8749b1fe 100644 --- a/packages/edit-post/src/components/sidebar/post-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-status/index.js @@ -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 @@ -29,7 +30,7 @@ function PostStatus( { isOpened, onTogglePanel } ) { return ( @@ -38,6 +39,7 @@ function PostStatus( { isOpened, onTogglePanel } ) { <> + diff --git a/packages/edit-post/src/components/sidebar/post-template/create-modal.js b/packages/edit-post/src/components/sidebar/post-template/create-modal.js new file mode 100644 index 0000000000000..8ae844c000730 --- /dev/null +++ b/packages/edit-post/src/components/sidebar/post-template/create-modal.js @@ -0,0 +1,146 @@ +/** + * 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'; +import { cleanForSlug } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import { store as editPostStore } from '../../../store'; + +const DEFAULT_TITLE = __( 'Custom Template' ); + +export default function PostTemplateCreateModal( { onClose } ) { + const defaultBlockTemplate = useSelect( + ( select ) => + select( editorStore ).getEditorSettings().defaultBlockTemplate, + [] + ); + + 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 = + 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: cleanForSlug( title || DEFAULT_TITLE ), + content: newTemplateContent, + title: title || DEFAULT_TITLE, + } ); + + setIsBusy( false ); + cancel(); + + __unstableSwitchToTemplateMode( true ); + }; + + return ( + +
+ + + + + + + + + + + + + + +
+
+ ); +} diff --git a/packages/edit-post/src/components/sidebar/post-template/form.js b/packages/edit-post/src/components/sidebar/post-template/form.js new file mode 100644 index 0000000000000..71c7a0e3d630a --- /dev/null +++ b/packages/edit-post/src/components/sidebar/post-template/form.js @@ -0,0 +1,140 @@ +/** + * WordPress dependencies + */ +import { useState, useMemo } from '@wordpress/element'; +import { __experimentalInspectorPopoverHeader as InspectorPopoverHeader } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; +import { addTemplate } from '@wordpress/icons'; +import { Notice, SelectControl, Button } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { store as editorStore } from '@wordpress/editor'; +import { store as coreStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import { store as editPostStore } from '../../../store'; +import PostTemplateCreateModal from './create-modal'; + +export default function PostTemplateForm( { onClose } ) { + const { + isPostsPage, + availableTemplates, + fetchedTemplates, + selectedTemplateSlug, + canCreate, + canEdit, + } = useSelect( ( select ) => { + const editorSettings = select( editorStore ).getEditorSettings(); + const siteSettings = select( coreStore ).getEntityRecord( + 'root', + 'site' + ); + const _isPostsPage = + select( editorStore ).getCurrentPostId() === + siteSettings?.page_for_posts; + const canCreateTemplates = select( coreStore ).canUser( + 'create', + 'templates' + ); + return { + isPostsPage: _isPostsPage, + availableTemplates: editorSettings.availableTemplates, + fetchedTemplates: select( coreStore ).getEntityRecords( + 'postType', + 'wp_template', + { + post_type: select( editorStore ).getCurrentPostType(), + per_page: -1, + } + ), + selectedTemplateSlug: + select( editorStore ).getEditedPostAttribute( 'template' ), + canCreate: canCreateTemplates && ! _isPostsPage, + canEdit: + canCreateTemplates && + editorSettings.supportsTemplateMode && + !! select( editPostStore ).getEditedPostTemplate(), + }; + }, [] ); + + const options = useMemo( + () => + Object.entries( { + ...availableTemplates, + ...Object.fromEntries( + ( fetchedTemplates ?? [] ).map( ( { slug, title } ) => [ + slug, + title.rendered, + ] ) + ), + } ).map( ( [ slug, title ] ) => ( { value: slug, label: title } ) ), + [ availableTemplates, fetchedTemplates ] + ); + + const selectedOption = + options.find( ( option ) => option.value === selectedTemplateSlug ) ?? + options.find( ( option ) => ! option.value ); // The default option has '' value. + + const { editPost } = useDispatch( editorStore ); + const { __unstableSwitchToTemplateMode } = useDispatch( editPostStore ); + + const [ isCreateModalOpen, setIsCreateModalOpen ] = useState( false ); + + return ( +
+ setIsCreateModalOpen( true ), + }, + ] + : [] + } + onClose={ onClose } + /> + { isPostsPage ? ( + + { __( 'The posts page template cannot be changed.' ) } + + ) : ( + + editPost( { template: slug || '' } ) + } + /> + ) } + { canEdit && ( +

+ +

+ ) } + { isCreateModalOpen && ( + setIsCreateModalOpen( false ) } + /> + ) } +
+ ); +} diff --git a/packages/edit-post/src/components/sidebar/post-template/index.js b/packages/edit-post/src/components/sidebar/post-template/index.js new file mode 100644 index 0000000000000..bce4c6a7858a5 --- /dev/null +++ b/packages/edit-post/src/components/sidebar/post-template/index.js @@ -0,0 +1,101 @@ +/** + * WordPress dependencies + */ +import { useRef } from '@wordpress/element'; +import { PanelRow, Dropdown, Button } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { store as editorStore } from '@wordpress/editor'; +import { store as coreStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import PostTemplateForm from './form'; + +export default function PostTemplate() { + const anchorRef = useRef(); + + const isVisible = useSelect( ( select ) => { + const postTypeSlug = select( editorStore ).getCurrentPostType(); + const postType = select( coreStore ).getPostType( postTypeSlug ); + if ( ! postType?.viewable ) { + return false; + } + + const settings = select( editorStore ).getEditorSettings(); + const hasTemplates = + !! settings.availableTemplates && + Object.keys( settings.availableTemplates ).length > 0; + if ( hasTemplates ) { + return true; + } + + const canCreateTemplates = + select( coreStore ).canUser( 'create', 'templates' ) ?? false; + return canCreateTemplates; + }, [] ); + + if ( ! isVisible ) { + return null; + } + + return ( + + { __( 'Template' ) } + ( + + ) } + renderContent={ ( { onClose } ) => ( + + ) } + /> + + ); +} + +function PostTemplateToggle( { isOpen, onClick } ) { + const templateTitle = useSelect( ( select ) => { + const templateSlug = + select( editorStore ).getEditedPostAttribute( 'template' ); + + const settings = select( editorStore ).getEditorSettings(); + if ( settings.availableTemplates[ templateSlug ] ) { + return settings.availableTemplates[ templateSlug ]; + } + + const template = select( coreStore ) + .getEntityRecords( 'postType', 'wp_template', { per_page: -1 } ) + ?.find( ( { slug } ) => slug === templateSlug ); + + return template?.title.rendered; + }, [] ); + + return ( + + ); +} diff --git a/packages/edit-post/src/components/sidebar/post-template/style.scss b/packages/edit-post/src/components/sidebar/post-template/style.scss new file mode 100644 index 0000000000000..b44a8167a4760 --- /dev/null +++ b/packages/edit-post/src/components/sidebar/post-template/style.scss @@ -0,0 +1,46 @@ +.edit-post-post-template { + width: 100%; + justify-content: flex-start; + + span { + display: block; + width: 45%; + } +} + +.edit-post-post-template__dropdown { + max-width: 55%; +} + +.components-button.edit-post-post-template__toggle { + display: inline-block; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} + +.edit-post-post-template__dialog { + z-index: z-index(".edit-post-post-template__dialog"); +} + +.edit-post-post-template__form { + // sidebar width - popover padding - form margin + min-width: $sidebar-width - $grid-unit-20 - $grid-unit-20; + margin: $grid-unit-10; +} + +.edit-post-post-template__create-modal { + .components-modal__header { + border-bottom: none; + } + + .components-modal__content::before { + margin-bottom: $grid-unit-05; + } +} + +.edit-post-post-template__create-form { + @include break-medium() { + width: $grid-unit * 40; + } +} diff --git a/packages/edit-post/src/components/sidebar/post-visibility/style.scss b/packages/edit-post/src/components/sidebar/post-visibility/style.scss index 1009a3186449a..82c89688fca75 100644 --- a/packages/edit-post/src/components/sidebar/post-visibility/style.scss +++ b/packages/edit-post/src/components/sidebar/post-visibility/style.scss @@ -8,8 +8,8 @@ } } -.edit-post-post-visibility__dialog .components-popover__content { - // sidebar width - padding - horizontal borders - width: $sidebar-width - $grid-unit-20 - $border-width * 2; - padding: $grid-unit-20; +.edit-post-post-visibility__dialog .editor-post-visibility { + // sidebar width - popover padding - form margin + min-width: $sidebar-width - $grid-unit-20 - $grid-unit-20; + margin: $grid-unit-10; } diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index c6f5234e33cd5..23943b9c00599 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -25,7 +25,6 @@ import PageAttributes from '../page-attributes'; import MetaBoxes from '../../meta-boxes'; import PluginDocumentSettingPanel from '../plugin-document-setting-panel'; import PluginSidebarEditPost from '../plugin-sidebar'; -import Template from '../template'; import TemplateSummary from '../template-summary'; import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; @@ -86,7 +85,6 @@ const SettingsSidebar = () => { { ! isTemplateMode && sidebarName === 'edit-post/document' && ( <> -