diff --git a/packages/edit-post/src/components/header/document-title/index.js b/packages/edit-post/src/components/header/document-title/index.js
new file mode 100644
index 00000000000000..1b27a0bacf014b
--- /dev/null
+++ b/packages/edit-post/src/components/header/document-title/index.js
@@ -0,0 +1,89 @@
+/**
+ * WordPress dependencies
+ */
+import { __, isRTL } from '@wordpress/i18n';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { BlockIcon, store as blockEditorStore } from '@wordpress/block-editor';
+import {
+ Button,
+ VisuallyHidden,
+ __experimentalHStack as HStack,
+ __experimentalText as Text,
+} from '@wordpress/components';
+import { layout, chevronLeftSmall, chevronRightSmall } from '@wordpress/icons';
+import { privateApis as commandsPrivateApis } from '@wordpress/commands';
+import { displayShortcut } from '@wordpress/keycodes';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../../private-apis';
+import { store as editPostStore } from '../../../store';
+
+const { store: commandsStore } = unlock( commandsPrivateApis );
+
+function DocumentTitle() {
+ const { template, isEditing } = useSelect( ( select ) => {
+ const { isEditingTemplate, getEditedPostTemplate } =
+ select( editPostStore );
+ const _isEditing = isEditingTemplate();
+
+ return {
+ template: _isEditing ? getEditedPostTemplate() : null,
+ isEditing: _isEditing,
+ };
+ }, [] );
+ const { clearSelectedBlock } = useDispatch( blockEditorStore );
+ const { setIsEditingTemplate } = useDispatch( editPostStore );
+ const { open: openCommandCenter } = useDispatch( commandsStore );
+
+ if ( ! isEditing || ! template ) {
+ return null;
+ }
+
+ let templateTitle = __( 'Default' );
+ if ( template?.title ) {
+ templateTitle = template.title;
+ } else if ( !! template ) {
+ templateTitle = template.slug;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+export default DocumentTitle;
diff --git a/packages/edit-post/src/components/header/document-title/style.scss b/packages/edit-post/src/components/header/document-title/style.scss
new file mode 100644
index 00000000000000..e39ecf607e4306
--- /dev/null
+++ b/packages/edit-post/src/components/header/document-title/style.scss
@@ -0,0 +1,61 @@
+.edit-post-document-title {
+ display: flex;
+ align-items: center;
+ gap: $grid-unit;
+ height: $button-size;
+ justify-content: space-between;
+ // Flex items will, by default, refuse to shrink below a minimum
+ // intrinsic width. In order to shrink this flexbox item, and
+ // subsequently truncate child text, we set an explicit min-width.
+ // See https://dev.w3.org/csswg/css-flexbox/#min-size-auto
+ min-width: 0;
+ background: $gray-100;
+ border-radius: 4px;
+ width: min(100%, 450px);
+
+ &:hover {
+ color: currentColor;
+ background: $gray-200;
+ }
+}
+
+.edit-post-document-title__title.components-button {
+ flex-grow: 1;
+ color: var(--wp-block-synced-color);
+ overflow: hidden;
+
+ &:hover {
+ color: var(--wp-block-synced-color);
+ }
+
+ h1 {
+ color: var(--wp-block-synced-color);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+}
+
+.edit-post-document-title__shortcut {
+ flex-shrink: 0;
+ color: $gray-700;
+ padding: 0 $grid-unit-15;
+
+ &:hover {
+ color: $gray-700;
+ }
+}
+
+.edit-post-document-title__left {
+ min-width: $button-size;
+ flex-shrink: 0;
+
+ .components-button.has-icon.has-text {
+ color: $gray-700;
+ gap: 0;
+
+ &:hover {
+ color: currentColor;
+ }
+ }
+}
diff --git a/packages/edit-post/src/components/header/header-toolbar/style.scss b/packages/edit-post/src/components/header/header-toolbar/style.scss
index 87aec00004c02b..694dcb5a2d678a 100644
--- a/packages/edit-post/src/components/header/header-toolbar/style.scss
+++ b/packages/edit-post/src/components/header/header-toolbar/style.scss
@@ -1,6 +1,5 @@
.edit-post-header-toolbar {
display: inline-flex;
- flex-grow: 1;
align-items: center;
border: none;
diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js
index 09a93424f6903d..3306a0fdf1606a 100644
--- a/packages/edit-post/src/components/header/index.js
+++ b/packages/edit-post/src/components/header/index.js
@@ -18,7 +18,7 @@ import { default as DevicePreview } from '../device-preview';
import ViewLink from '../view-link';
import MainDashboardButton from './main-dashboard-button';
import { store as editPostStore } from '../../store';
-import TemplateTitle from './template-title';
+import DocumentTitle from './document-title';
function Header( { setEntitiesSavedStatesCallback } ) {
const isLargeViewport = useViewportMatch( 'large' );
@@ -70,7 +70,9 @@ function Header( { setEntitiesSavedStatesCallback } ) {
className="edit-post-header__toolbar"
>
-
+
+
+
{
- const { isEditingTemplate, getEditedPostTemplate } =
- select( editPostStore );
- const _isEditing = isEditingTemplate();
- return {
- template: _isEditing ? getEditedPostTemplate() : null,
- };
- }, [] );
- const [ showConfirmDialog, setShowConfirmDialog ] = useState( false );
-
- if ( ! template || ! template.wp_id ) {
- return null;
- }
- let templateTitle = template.slug;
- if ( template?.title ) {
- templateTitle = template.title;
- }
-
- const isRevertable = template?.has_theme_file;
-
- const onDelete = () => {
- clearSelectedBlock();
- setIsEditingTemplate( false );
- setShowConfirmDialog( false );
-
- editPost( {
- template: '',
- } );
- const settings = getEditorSettings();
- const newAvailableTemplates = Object.fromEntries(
- Object.entries( settings.availableTemplates ?? {} ).filter(
- ( [ id ] ) => id !== template.slug
- )
- );
- updateEditorSettings( {
- availableTemplates: newAvailableTemplates,
- } );
- deleteEntityRecord( 'postType', 'wp_template', template.id, {
- throwOnError: true,
- } );
- };
-
- return (
-
- <>
-
- {
- setShowConfirmDialog( false );
- } }
- >
- { sprintf(
- /* translators: %s: template name */
- __(
- 'Are you sure you want to delete the %s template? It may be used by other pages or posts.'
- ),
- templateTitle
- ) }
-
- >
-
- );
-}
diff --git a/packages/edit-post/src/components/header/template-title/edit-template-title.js b/packages/edit-post/src/components/header/template-title/edit-template-title.js
deleted file mode 100644
index 447ea5e4e02d72..00000000000000
--- a/packages/edit-post/src/components/header/template-title/edit-template-title.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { __ } from '@wordpress/i18n';
-import { TextControl } from '@wordpress/components';
-import { useDispatch, useSelect } from '@wordpress/data';
-import { useState } from '@wordpress/element';
-import { store as editorStore } from '@wordpress/editor';
-import { store as coreStore } from '@wordpress/core-data';
-
-/**
- * Internal dependencies
- */
-import { store as editPostStore } from '../../../store';
-
-export default function EditTemplateTitle() {
- const [ forceEmpty, setForceEmpty ] = useState( false );
- const { template } = useSelect( ( select ) => {
- const { getEditedPostTemplate } = select( editPostStore );
- return {
- template: getEditedPostTemplate(),
- };
- }, [] );
-
- const { editEntityRecord } = useDispatch( coreStore );
- const { getEditorSettings } = useSelect( editorStore );
- const { updateEditorSettings } = useDispatch( editorStore );
-
- // Only user-created and non-default templates can change the name.
- if ( ! template.is_custom || template.has_theme_file ) {
- return null;
- }
-
- let templateTitle = __( 'Default' );
- if ( template?.title ) {
- templateTitle = template.title;
- } else if ( !! template ) {
- templateTitle = template.slug;
- }
-
- return (
-
- {
- // Allow having the field temporarily empty while typing.
- if ( ! newTitle && ! forceEmpty ) {
- setForceEmpty( true );
- return;
- }
- setForceEmpty( false );
-
- const settings = getEditorSettings();
- const newAvailableTemplates = Object.fromEntries(
- Object.entries( settings.availableTemplates ?? {} ).map(
- ( [ id, existingTitle ] ) => [
- id,
- id !== template.slug ? existingTitle : newTitle,
- ]
- )
- );
- updateEditorSettings( {
- availableTemplates: newAvailableTemplates,
- } );
- editEntityRecord( 'postType', 'wp_template', template.id, {
- title: newTitle,
- } );
- } }
- onBlur={ () => setForceEmpty( false ) }
- />
-
- );
-}
diff --git a/packages/edit-post/src/components/header/template-title/index.js b/packages/edit-post/src/components/header/template-title/index.js
deleted file mode 100644
index c0745dc0451b74..00000000000000
--- a/packages/edit-post/src/components/header/template-title/index.js
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { __, sprintf } from '@wordpress/i18n';
-import { useSelect, useDispatch } from '@wordpress/data';
-import {
- Dropdown,
- Button,
- __experimentalText as Text,
-} from '@wordpress/components';
-import { chevronDown } from '@wordpress/icons';
-
-/**
- * Internal dependencies
- */
-import { store as editPostStore } from '../../../store';
-import { store as blockEditorStore } from '@wordpress/block-editor';
-import { store as editorStore } from '@wordpress/editor';
-import DeleteTemplate from './delete-template';
-import EditTemplateTitle from './edit-template-title';
-import TemplateDescription from './template-description';
-
-function TemplateTitle() {
- const { template, isEditing, title } = useSelect( ( select ) => {
- const { isEditingTemplate, getEditedPostTemplate } =
- select( editPostStore );
- const { getEditedPostAttribute } = select( editorStore );
-
- const _isEditing = isEditingTemplate();
-
- return {
- template: _isEditing ? getEditedPostTemplate() : null,
- isEditing: _isEditing,
- title: getEditedPostAttribute( 'title' )
- ? getEditedPostAttribute( 'title' )
- : __( 'Untitled' ),
- };
- }, [] );
-
- const { clearSelectedBlock } = useDispatch( blockEditorStore );
- const { setIsEditingTemplate } = useDispatch( editPostStore );
-
- if ( ! isEditing || ! template ) {
- return null;
- }
-
- let templateTitle = __( 'Default' );
- if ( template?.title ) {
- templateTitle = template.title;
- } else if ( !! template ) {
- templateTitle = template.slug;
- }
-
- const hasOptions = !! (
- template.custom ||
- template.wp_id ||
- template.description
- );
-
- return (
-
-
- { hasOptions ? (
- (
-
- ) }
- renderContent={ () => (
- <>
-
-
-
- >
- ) }
- />
- ) : (
-
- { templateTitle }
-
- ) }
-
- );
-}
-
-export default TemplateTitle;
diff --git a/packages/edit-post/src/components/header/template-title/style.scss b/packages/edit-post/src/components/header/template-title/style.scss
deleted file mode 100644
index b5fe5120bfb64c..00000000000000
--- a/packages/edit-post/src/components/header/template-title/style.scss
+++ /dev/null
@@ -1,94 +0,0 @@
-.edit-post-template-top-area {
- display: flex;
- flex-direction: column;
- align-content: space-between;
- width: 100%;
- align-items: center;
-
- .edit-post-template-title,
- .edit-post-template-post-title {
- padding: 0;
- text-decoration: none;
- height: auto;
-
- &::before {
- height: 100%;
- }
-
- &.has-icon {
- svg {
- order: 1;
- margin-right: 0;
- }
- }
- }
-
- .edit-post-template-title {
- color: $gray-900;
- }
-
- .edit-post-template-post-title {
- margin-top: $grid-unit-05;
- max-width: 160px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- display: block;
-
- &::before {
- left: 0;
- right: 0;
- }
-
- @include break-xlarge() {
- max-width: 400px;
- }
- }
-}
-
-.edit-post-template-top-area__popover {
- .components-popover__content {
- min-width: 280px;
- padding: 0;
- }
-
- .edit-site-template-details__group {
- padding: $grid-unit-20;
-
- .components-base-control__help {
- margin-bottom: 0;
- }
- }
-
- .edit-post-template-details__description {
- color: $gray-700;
- }
-}
-
-.edit-post-template-top-area__second-menu-group {
- border-top: $border-width solid $gray-300;
- padding: $grid-unit-20 $grid-unit-10;
-
- .edit-post-template-top-area__delete-template-button {
- display: flex;
- justify-content: center;
- padding: $grid-unit-05 $grid-unit;
-
- &.is-destructive {
- padding: inherit;
- margin-left: $grid-unit-10;
- margin-right: $grid-unit-10;
- width: calc(100% - #{($grid-unit * 2)});
-
- .components-menu-item__item {
- width: auto;
- }
- }
-
- .components-menu-item__item {
- margin-right: 0;
- min-width: 0;
- width: 100%;
- }
- }
-}
diff --git a/packages/edit-post/src/components/header/template-title/template-description.js b/packages/edit-post/src/components/header/template-title/template-description.js
deleted file mode 100644
index 3513496852c339..00000000000000
--- a/packages/edit-post/src/components/header/template-title/template-description.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { useSelect } from '@wordpress/data';
-import {
- __experimentalHeading as Heading,
- __experimentalText as Text,
-} from '@wordpress/components';
-
-/**
- * Internal dependencies
- */
-import { store as editPostStore } from '../../../store';
-
-export default function TemplateDescription() {
- const { description, title } = useSelect( ( select ) => {
- const { getEditedPostTemplate } = select( editPostStore );
- return {
- title: getEditedPostTemplate().title,
- description: getEditedPostTemplate().description,
- };
- }, [] );
- if ( ! description ) {
- return null;
- }
-
- return (
-
-
- { title }
-
-
- { description }
-
-
- );
-}
diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js
index ac8902f6a5f7a1..638a869aa8350c 100644
--- a/packages/edit-post/src/components/visual-editor/index.js
+++ b/packages/edit-post/src/components/visual-editor/index.js
@@ -31,11 +31,9 @@ import {
__experimentaluseLayoutStyles as useLayoutStyles,
} from '@wordpress/block-editor';
import { useEffect, useRef, useMemo } from '@wordpress/element';
-import { Button, __unstableMotion as motion } from '@wordpress/components';
-import { useSelect, useDispatch } from '@wordpress/data';
+import { __unstableMotion as motion } from '@wordpress/components';
+import { useSelect } from '@wordpress/data';
import { useMergeRefs } from '@wordpress/compose';
-import { arrowLeft } from '@wordpress/icons';
-import { __ } from '@wordpress/i18n';
import { parse } from '@wordpress/blocks';
import { store as coreStore } from '@wordpress/core-data';
@@ -175,8 +173,6 @@ export default function VisualEditor( { styles } ) {
_settings.__experimentalFeatures?.useRootPaddingAwareAlignments,
};
}, [] );
- const { clearSelectedBlock } = useDispatch( blockEditorStore );
- const { setIsEditingTemplate } = useDispatch( editPostStore );
const desktopCanvasStyles = {
height: '100%',
width: '100%',
@@ -349,18 +345,6 @@ export default function VisualEditor( { styles } ) {
} }
ref={ blockSelectionClearerRef }
>
- { isTemplateMode && (
-
- ) }
{
)
).toBeVisible();
} );
-
- test( 'Allow editing the title of a new custom template', async ( {
- page,
- postEditorTemplateMode,
- } ) => {
- async function editTemplateTitle( newTitle ) {
- await page
- .getByRole( 'button', { name: 'Template Options' } )
- .click();
-
- await page
- .getByRole( 'textbox', { name: 'Title' } )
- .fill( newTitle );
-
- const editorContent = page.getByLabel( 'Editor Content' );
- await editorContent.click();
- }
-
- await postEditorTemplateMode.createPostAndSaveDraft();
- await postEditorTemplateMode.createNewTemplate( 'Foobar' );
- await editTemplateTitle( 'Barfoo' );
-
- await expect(
- page.getByRole( 'button', { name: 'Template Options' } )
- ).toHaveText( 'Barfoo' );
- } );
-
- test.describe( 'Delete Post Template Confirmation Dialog', () => {
- test.beforeAll( async ( { requestUtils } ) => {
- await requestUtils.activateTheme( 'twentytwentyone' );
- } );
-
- test.beforeEach( async ( { postEditorTemplateMode } ) => {
- await postEditorTemplateMode.createPostAndSaveDraft();
- } );
-
- [ 'large', 'small' ].forEach( ( viewport ) => {
- test( `should retain template if deletion is canceled when the viewport is ${ viewport }`, async ( {
- editor,
- page,
- pageUtils,
- postEditorTemplateMode,
- } ) => {
- await pageUtils.setBrowserViewport( viewport );
-
- await postEditorTemplateMode.disableTemplateWelcomeGuide();
-
- const templateTitle = `${ viewport } Viewport Deletion Test`;
-
- await postEditorTemplateMode.createNewTemplate( templateTitle );
-
- // Close the settings in small viewport.
- if ( viewport === 'small' ) {
- await page.click( 'role=button[name="Close settings"i]' );
- }
-
- // Edit the template.
- await editor.insertBlock( { name: 'core/paragraph' } );
- await page.keyboard.type(
- 'Just a random paragraph added to the template'
- );
-
- await postEditorTemplateMode.saveTemplateWithoutPublishing();
-
- // Test deletion dialog.
- {
- const templateDropdown =
- postEditorTemplateMode.editorTopBar.locator(
- 'role=button[name="Template Options"i]'
- );
- await templateDropdown.click();
- await page.click(
- 'role=menuitem[name="Delete template"i]'
- );
-
- const confirmDeletionDialog = page.locator( 'role=dialog' );
- await expect( confirmDeletionDialog ).toBeFocused();
- await expect(
- confirmDeletionDialog.locator(
- `text=Are you sure you want to delete the ${ templateTitle } template? It may be used by other pages or posts.`
- )
- ).toBeVisible();
-
- await confirmDeletionDialog
- .locator( 'role=button[name="Cancel"i]' )
- .click();
- }
-
- // Exit template mode.
- await page.click( 'role=button[name="Back"i]' );
-
- await editor.openDocumentSettingsSidebar();
-
- // Move focus to the "Post" panel in the editor sidebar.
- const postPanel =
- postEditorTemplateMode.editorSettingsSidebar.locator(
- 'role=button[name="Post"i]'
- );
- await postPanel.click();
-
- await postEditorTemplateMode.openTemplatePopover();
-
- const templateSelect = page.locator(
- 'role=combobox[name="Template"i]'
- );
- await expect( templateSelect ).toHaveValue(
- `${ viewport }-viewport-deletion-test`
- );
- } );
-
- test( `should delete template if deletion is confirmed when the viewport is ${ viewport }`, async ( {
- editor,
- page,
- pageUtils,
- postEditorTemplateMode,
- } ) => {
- const templateTitle = `${ viewport } Viewport Deletion Test`;
-
- await pageUtils.setBrowserViewport( viewport );
-
- await postEditorTemplateMode.createNewTemplate( templateTitle );
-
- // Close the settings in small viewport.
- if ( viewport === 'small' ) {
- await page.click( 'role=button[name="Close settings"i]' );
- }
-
- // Edit the template.
- await editor.insertBlock( { name: 'core/paragraph' } );
- await page.keyboard.type(
- 'Just a random paragraph added to the template'
- );
-
- await postEditorTemplateMode.saveTemplateWithoutPublishing();
-
- {
- const templateDropdown =
- postEditorTemplateMode.editorTopBar.locator(
- 'role=button[name="Template Options"i]'
- );
- await templateDropdown.click();
- await page.click(
- 'role=menuitem[name="Delete template"i]'
- );
-
- const confirmDeletionDialog = page.locator( 'role=dialog' );
- await expect( confirmDeletionDialog ).toBeFocused();
- await expect(
- confirmDeletionDialog.locator(
- `text=Are you sure you want to delete the ${ templateTitle } template? It may be used by other pages or posts.`
- )
- ).toBeVisible();
-
- await confirmDeletionDialog
- .locator( 'role=button[name="OK"i]' )
- .click();
- }
-
- // Saving isn't technically necessary, but for themes without any specified templates,
- // the removal of the Templates dropdown is delayed. A save and reload allows for this
- // delay and prevents flakiness
- {
- await page.click( 'role=button[name="Save draft"i]' );
- await page.waitForSelector(
- 'role=button[name="Dismiss this notice"] >> text=Draft saved'
- );
- await page.reload();
- }
-
- const templateOptions =
- postEditorTemplateMode.editorSettingsSidebar.locator(
- 'role=combobox[name="Template:"i] >> role=menuitem'
- );
- const availableTemplates =
- await templateOptions.allTextContents();
-
- expect( availableTemplates ).not.toContain(
- `${ viewport } Viewport Deletion Test`
- );
- } );
- } );
- } );
} );
class PostEditorTemplateMode {
@@ -331,7 +149,9 @@ class PostEditorTemplateMode {
'role=button[name="Dismiss this notice"] >> text=Editing template. Changes made here affect all posts and pages that use the template.'
);
- await expect( this.editorTopBar ).toHaveText( /Just an FSE Post/ );
+ await expect(
+ this.editorTopBar.getByRole( 'heading[level=1]' )
+ ).toHaveText( 'Editing template: Singular' );
}
async createPostAndSaveDraft() {