@@ -194,33 +196,35 @@ function ListItem< Item >( {
item={ item }
/>
) }
-
-
- }
+ { ! hasOnlyOnePrimaryAction && (
+
+
+ }
+ />
+ }
+ placement="bottom-end"
+ >
+
- }
- placement="bottom-end"
- >
-
-
-
+
+
+ ) }
);
diff --git a/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js b/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js
index b8125e96c7c2c..f3db9d123f9d7 100644
--- a/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js
+++ b/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js
@@ -4,7 +4,7 @@
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
-import { withSelect } from '@wordpress/data';
+import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { privateApis as preferencesPrivateApis } from '@wordpress/preferences';
import { getPathAndQueryString } from '@wordpress/url';
@@ -57,7 +57,10 @@ export function CustomFieldsConfirmation( { willEnable } ) {
);
}
-export function EnableCustomFieldsOption( { label, areCustomFieldsEnabled } ) {
+export default function EnableCustomFieldsOption( { label } ) {
+ const areCustomFieldsEnabled = useSelect( ( select ) => {
+ return !! select( editorStore ).getEditorSettings().enableCustomFields;
+ }, [] );
const [ isChecked, setIsChecked ] = useState( areCustomFieldsEnabled );
return (
@@ -72,8 +75,3 @@ export function EnableCustomFieldsOption( { label, areCustomFieldsEnabled } ) {
);
}
-
-export default withSelect( ( select ) => ( {
- areCustomFieldsEnabled:
- !! select( editorStore ).getEditorSettings().enableCustomFields,
-} ) )( EnableCustomFieldsOption );
diff --git a/packages/edit-post/src/components/preferences-modal/test/enable-custom-fields.js b/packages/edit-post/src/components/preferences-modal/test/enable-custom-fields.js
index 2dbeadec8350a..adfa4a3df391d 100644
--- a/packages/edit-post/src/components/preferences-modal/test/enable-custom-fields.js
+++ b/packages/edit-post/src/components/preferences-modal/test/enable-custom-fields.js
@@ -4,27 +4,38 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
+/**
+ * WordPress dependencies
+ */
+import { useSelect } from '@wordpress/data';
+
/**
* Internal dependencies
*/
import {
- EnableCustomFieldsOption,
+ default as EnableCustomFieldsOption,
CustomFieldsConfirmation,
} from '../enable-custom-fields';
+jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() );
+
+function setupUseSelectMock( areCustomFieldsEnabled ) {
+ useSelect.mockImplementation( () => {
+ return areCustomFieldsEnabled;
+ } );
+}
+
describe( 'EnableCustomFieldsOption', () => {
it( 'renders a checked checkbox when custom fields are enabled', () => {
- const { container } = render(
-
- );
+ setupUseSelectMock( true );
+ const { container } = render(
);
expect( container ).toMatchSnapshot();
} );
it( 'renders an unchecked checkbox when custom fields are disabled', () => {
- const { container } = render(
-
- );
+ setupUseSelectMock( false );
+ const { container } = render(
);
expect( container ).toMatchSnapshot();
} );
@@ -32,9 +43,8 @@ describe( 'EnableCustomFieldsOption', () => {
it( 'renders an unchecked checkbox and a confirmation message when toggled off', async () => {
const user = userEvent.setup();
- const { container } = render(
-
- );
+ setupUseSelectMock( true );
+ const { container } = render(
);
await user.click( screen.getByRole( 'checkbox' ) );
@@ -44,9 +54,8 @@ describe( 'EnableCustomFieldsOption', () => {
it( 'renders a checked checkbox and a confirmation message when toggled on', async () => {
const user = userEvent.setup();
- const { container } = render(
-
- );
+ setupUseSelectMock( false );
+ const { container } = render(
);
await user.click( screen.getByRole( 'checkbox' ) );
diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js
index fbff29ed67afa..a535eef4ce787 100644
--- a/packages/edit-site/src/components/post-edit/index.js
+++ b/packages/edit-site/src/components/post-edit/index.js
@@ -18,10 +18,9 @@ import { privateApis as editorPrivateApis } from '@wordpress/editor';
* Internal dependencies
*/
import Page from '../page';
-import usePostFields from '../post-fields';
import { unlock } from '../../lock-unlock';
-const { PostCardPanel } = unlock( editorPrivateApis );
+const { PostCardPanel, usePostFields } = unlock( editorPrivateApis );
const fieldsWithBulkEditSupport = [
'title',
diff --git a/packages/edit-site/src/components/post-fields/index.js b/packages/edit-site/src/components/post-fields/index.js
deleted file mode 100644
index 6ba9453709f0d..0000000000000
--- a/packages/edit-site/src/components/post-fields/index.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * WordPress dependencies
- */
-import {
- featuredImageField,
- slugField,
- parentField,
- passwordField,
- statusField,
- commentStatusField,
- titleField,
- dateField,
- authorField,
-} from '@wordpress/fields';
-import { useMemo } from '@wordpress/element';
-import { useEntityRecords } from '@wordpress/core-data';
-
-function usePostFields() {
- const { records: authors, isResolving: isLoadingAuthors } =
- useEntityRecords( 'root', 'user', { per_page: -1 } );
-
- const fields = useMemo(
- () => [
- featuredImageField,
- titleField,
- {
- ...authorField,
- elements:
- authors?.map( ( { id, name } ) => ( {
- value: id,
- label: name,
- } ) ) || [],
- },
- statusField,
- dateField,
- slugField,
- parentField,
- commentStatusField,
- passwordField,
- ],
- [ authors ]
- );
-
- return {
- isLoading: isLoadingAuthors,
- fields,
- };
-}
-
-export default usePostFields;
diff --git a/packages/edit-site/src/components/post-fields/style.scss b/packages/edit-site/src/components/post-fields/style.scss
deleted file mode 100644
index adeaf9a267825..0000000000000
--- a/packages/edit-site/src/components/post-fields/style.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.components-popover.components-dropdown__content.dataforms-layouts-panel__field-dropdown {
- z-index: z-index(".components-popover.components-dropdown__content.dataforms-layouts-panel__field-dropdown");
-}
diff --git a/packages/edit-site/src/components/post-list/index.js b/packages/edit-site/src/components/post-list/index.js
index 4639cb3c950b7..975809b2ad610 100644
--- a/packages/edit-site/src/components/post-list/index.js
+++ b/packages/edit-site/src/components/post-list/index.js
@@ -32,9 +32,8 @@ import AddNewPostModal from '../add-new-post';
import { unlock } from '../../lock-unlock';
import { useEditPostAction } from '../dataviews-actions';
import { usePrevious } from '@wordpress/compose';
-import usePostFields from '../post-fields';
-const { usePostActions } = unlock( editorPrivateApis );
+const { usePostActions, usePostFields } = unlock( editorPrivateApis );
const { useLocation, useHistory } = unlock( routerPrivateApis );
const { useEntityRecordsWithPermissions } = unlock( coreDataPrivateApis );
const EMPTY_ARRAY = [];
diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss
index 0e5744fe362e3..63ad8244a7c95 100644
--- a/packages/edit-site/src/style.scss
+++ b/packages/edit-site/src/style.scss
@@ -30,7 +30,6 @@
@import "./components/editor-canvas-container/style.scss";
@import "./components/post-edit/style.scss";
@import "./components/post-list/style.scss";
-@import "./components/post-fields/style.scss";
@import "./components/resizable-frame/style.scss";
@import "./hooks/push-changes-to-global-styles/style.scss";
@import "./components/global-styles/font-library-modal/style.scss";
diff --git a/packages/editor/src/components/post-featured-image/index.js b/packages/editor/src/components/post-featured-image/index.js
index cabd791e938bf..46a194f311a5e 100644
--- a/packages/editor/src/components/post-featured-image/index.js
+++ b/packages/editor/src/components/post-featured-image/index.js
@@ -1,3 +1,8 @@
+/**
+ * External dependencies
+ */
+import clsx from 'clsx';
+
/**
* WordPress dependencies
*/
@@ -10,9 +15,10 @@ import {
withNotices,
withFilters,
__experimentalHStack as HStack,
+ Notice,
} from '@wordpress/components';
import { isBlobURL } from '@wordpress/blob';
-import { useState, useRef } from '@wordpress/element';
+import { useState, useRef, useEffect } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { useSelect, withDispatch, withSelect } from '@wordpress/data';
import {
@@ -94,11 +100,19 @@ function PostFeaturedImage( {
postType,
noticeUI,
noticeOperations,
+ isRequestingFeaturedImageMedia,
} ) {
const toggleRef = useRef();
const [ isLoading, setIsLoading ] = useState( false );
const { getSettings } = useSelect( blockEditorStore );
const { mediaSourceUrl } = getMediaDetails( media, currentPostId );
+ const toggleFocusTimerRef = useRef();
+
+ useEffect( () => {
+ return () => {
+ clearTimeout( toggleFocusTimerRef.current );
+ };
+ }, [] );
function onDropFiles( filesList ) {
getSettings().mediaUpload( {
@@ -150,6 +164,9 @@ function PostFeaturedImage( {
);
}
+ const isMissingMedia =
+ ! isRequestingFeaturedImageMedia && !! featuredImageId && ! media;
+
return (
{ noticeUI }
@@ -174,52 +191,83 @@ function PostFeaturedImage( {
modalClass="editor-post-featured-image__media-modal"
render={ ( { open } ) => (
-
+ { isMissingMedia ? (
+
+ { __(
+ 'Could not retrieve the featured image data.'
+ ) }
+
+ ) : (
+
+ ) }
{ !! featuredImageId && (
-
+
@@ -228,8 +276,19 @@ function PostFeaturedImage( {
className="editor-post-featured-image__action"
onClick={ () => {
onRemoveImage();
- toggleRef.current.focus();
+ // The toggle button is rendered conditionally, we need
+ // to wait it is rendered before moving focus to it.
+ toggleFocusTimerRef.current =
+ setTimeout( () => {
+ toggleRef.current?.focus();
+ } );
} }
+ variant={
+ isMissingMedia
+ ? 'secondary'
+ : undefined
+ }
+ isDestructive={ isMissingMedia }
>
{ __( 'Remove' ) }
@@ -247,7 +306,8 @@ function PostFeaturedImage( {
}
const applyWithSelect = withSelect( ( select ) => {
- const { getMedia, getPostType } = select( coreStore );
+ const { getMedia, getPostType, hasFinishedResolution } =
+ select( coreStore );
const { getCurrentPostId, getEditedPostAttribute } = select( editorStore );
const featuredImageId = getEditedPostAttribute( 'featured_media' );
@@ -258,6 +318,12 @@ const applyWithSelect = withSelect( ( select ) => {
currentPostId: getCurrentPostId(),
postType: getPostType( getEditedPostAttribute( 'type' ) ),
featuredImageId,
+ isRequestingFeaturedImageMedia:
+ !! featuredImageId &&
+ ! hasFinishedResolution( 'getMedia', [
+ featuredImageId,
+ { context: 'view' },
+ ] ),
};
} );
diff --git a/packages/editor/src/components/post-featured-image/style.scss b/packages/editor/src/components/post-featured-image/style.scss
index 30d5cb43403cd..bf9433faa662d 100644
--- a/packages/editor/src/components/post-featured-image/style.scss
+++ b/packages/editor/src/components/post-featured-image/style.scss
@@ -16,11 +16,16 @@
&:hover,
&:focus,
&:focus-within {
- .editor-post-featured-image__actions {
+ .editor-post-featured-image__actions:not(.editor-post-featured-image__actions-is-requesting-image) {
opacity: 1;
}
}
+ .editor-post-featured-image__actions.editor-post-featured-image__actions-missing-image {
+ opacity: 1;
+ margin-top: $grid-unit-20;
+ }
+
.components-drop-zone__content {
border-radius: $radius-small;
}
@@ -72,17 +77,22 @@
}
.editor-post-featured-image__actions {
- @include reduce-motion("transition");
- bottom: 0;
- opacity: 0; // Use opacity instead of visibility so that the buttons remain in the tab order.
- padding: $grid-unit-10;
- position: absolute;
- transition: opacity 50ms ease-out;
-}
+ &:not(.editor-post-featured-image__actions-missing-image) {
+ @include reduce-motion("transition");
+ bottom: 0;
+ opacity: 0; // Use opacity instead of visibility so that the buttons remain in the tab order.
+ padding: $grid-unit-10;
+ position: absolute;
+ transition: opacity 50ms ease-out;
-.editor-post-featured-image__action {
- backdrop-filter: blur(16px) saturate(180%);
- background: rgba(255, 255, 255, 0.75);
- flex-grow: 1;
- justify-content: center;
+ .editor-post-featured-image__action {
+ backdrop-filter: blur(16px) saturate(180%);
+ background: rgba(255, 255, 255, 0.75);
+ }
+ }
+
+ .editor-post-featured-image__action {
+ flex-grow: 1;
+ justify-content: center;
+ }
}
diff --git a/packages/editor/src/components/post-fields/index.ts b/packages/editor/src/components/post-fields/index.ts
new file mode 100644
index 0000000000000..3d675ab763d64
--- /dev/null
+++ b/packages/editor/src/components/post-fields/index.ts
@@ -0,0 +1,65 @@
+/**
+ * WordPress dependencies
+ */
+import { useMemo } from '@wordpress/element';
+import { useEntityRecords } from '@wordpress/core-data';
+import type { Field } from '@wordpress/dataviews';
+import {
+ featuredImageField,
+ slugField,
+ parentField,
+ passwordField,
+ statusField,
+ commentStatusField,
+ titleField,
+ dateField,
+ authorField,
+} from '@wordpress/fields';
+import type { BasePostWithEmbeddedAuthor } from '@wordpress/fields';
+
+interface UsePostFieldsReturn {
+ isLoading: boolean;
+ fields: Field< BasePostWithEmbeddedAuthor >[];
+}
+
+interface Author {
+ id: number;
+ name: string;
+}
+
+function usePostFields(): UsePostFieldsReturn {
+ const { records: authors, isResolving: isLoadingAuthors } =
+ useEntityRecords< Author >( 'root', 'user', { per_page: -1 } );
+
+ const fields = useMemo(
+ () =>
+ [
+ featuredImageField,
+ titleField,
+ {
+ ...authorField,
+ elements: authors?.map( ( { id, name } ) => ( {
+ value: id,
+ label: name,
+ } ) ),
+ },
+ statusField,
+ dateField,
+ slugField,
+ parentField,
+ commentStatusField,
+ passwordField,
+ ] as Field< BasePostWithEmbeddedAuthor >[],
+ [ authors ]
+ );
+
+ return {
+ isLoading: isLoadingAuthors,
+ fields,
+ };
+}
+
+/**
+ * Hook to get the fields for a post (BasePost or BasePostWithEmbeddedAuthor).
+ */
+export default usePostFields;
diff --git a/packages/editor/src/private-apis.js b/packages/editor/src/private-apis.js
index f9a6d4d17904e..b49b2a69a3bf2 100644
--- a/packages/editor/src/private-apis.js
+++ b/packages/editor/src/private-apis.js
@@ -16,6 +16,7 @@ import PluginPostExcerpt from './components/post-excerpt/plugin';
import PostCardPanel from './components/post-card-panel';
import PreferencesModal from './components/preferences-modal';
import { usePostActions } from './components/post-actions/actions';
+import usePostFields from './components/post-fields';
import ToolsMoreMenuGroup from './components/more-menu/tools-more-menu-group';
import ViewMoreMenuGroup from './components/more-menu/view-more-menu-group';
import ResizableEditor from './components/resizable-editor';
@@ -40,6 +41,7 @@ lock( privateApis, {
PostCardPanel,
PreferencesModal,
usePostActions,
+ usePostFields,
ToolsMoreMenuGroup,
ViewMoreMenuGroup,
ResizableEditor,
diff --git a/packages/fields/src/index.ts b/packages/fields/src/index.ts
index 4c721b85b61a4..41879a86e76be 100644
--- a/packages/fields/src/index.ts
+++ b/packages/fields/src/index.ts
@@ -1,2 +1,3 @@
export * from './fields';
export * from './actions';
+export type * from './types';
diff --git a/test/e2e/specs/editor/blocks/image.spec.js b/test/e2e/specs/editor/blocks/image.spec.js
index 6110a125ff6f7..b2195f2c67688 100644
--- a/test/e2e/specs/editor/blocks/image.spec.js
+++ b/test/e2e/specs/editor/blocks/image.spec.js
@@ -545,7 +545,7 @@ test.describe( 'Image', () => {
dummy.style.left = 0;
dummy.draggable = 'true';
dummy.addEventListener( 'dragstart', ( event ) => {
- event.dataTransfer.setData( 'text/html', _html );
+ event.dataTransfer.setData( 'default', _html );
setTimeout( () => {
dummy.remove();
}, 0 );