diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index 860341cc94626..8cd9051cbb1cc 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -2,7 +2,7 @@ * Internal dependencies */ import getFieldTypeDefinition from './field-types'; -import type { Field, NormalizedField } from './types'; +import type { Field, NormalizedField, ItemRecord } from './types'; /** * Apply default values and normalize the fields config. @@ -18,7 +18,8 @@ export function normalizeFields< Item >( const getValue = field.getValue || - ( ( { item }: { item: Item } ) => item[ field.id as keyof Item ] ); + // @ts-ignore + ( ( { item }: { item: ItemRecord } ) => item[ field.id ] ); const sort = field.sort ?? diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 25e603d8cfa23..64580a089997d 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -47,7 +47,7 @@ export type Operator = | 'isAll' | 'isNotAll'; -export type ItemRecord = Record< string, unknown >; +export type ItemRecord = Object; export type FieldType = 'text' | 'integer'; @@ -423,6 +423,12 @@ interface ActionBase< Item > { supportsBulk?: boolean; } +export interface RenderModalProps< Item > { + items: Item[]; + closeModal?: () => void; + onActionPerformed?: ( items: Item[] ) => void; +} + export interface ActionModal< Item > extends ActionBase< Item > { /** * Modal to render when the action is triggered. @@ -431,11 +437,7 @@ export interface ActionModal< Item > extends ActionBase< Item > { items, closeModal, onActionPerformed, - }: { - items: Item[]; - closeModal?: () => void; - onActionPerformed?: ( items: Item[] ) => void; - } ) => ReactElement; + }: RenderModalProps< Item > ) => ReactElement; /** * Whether to hide the modal header. diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 983cc33b29e62..64afec2417cd0 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -11,7 +11,7 @@ import { store as noticesStore } from '@wordpress/notices'; import { useMemo, useState, useEffect } from '@wordpress/element'; import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; import { parse } from '@wordpress/blocks'; -import { DataForm, isItemValid } from '@wordpress/dataviews'; +import { DataForm } from '@wordpress/dataviews'; import { Button, TextControl, @@ -58,10 +58,6 @@ const formDuplicateAction = { fields: [ 'title' ], }; -const formOrderAction = { - fields: [ 'menu_order' ], -}; - /** * Check if a template is removable. * @@ -238,110 +234,6 @@ const renamePostAction = { }, }; -function ReorderModal( { items, closeModal, onActionPerformed } ) { - const [ item, setItem ] = useState( items[ 0 ] ); - const orderInput = item.menu_order; - const { editEntityRecord, saveEditedEntityRecord } = - useDispatch( coreStore ); - const { createSuccessNotice, createErrorNotice } = - useDispatch( noticesStore ); - - async function onOrder( event ) { - event.preventDefault(); - - if ( ! isItemValid( item, fields, formOrderAction ) ) { - return; - } - - try { - await editEntityRecord( 'postType', item.type, item.id, { - menu_order: orderInput, - } ); - closeModal(); - // Persist edited entity. - await saveEditedEntityRecord( 'postType', item.type, item.id, { - throwOnError: true, - } ); - createSuccessNotice( __( 'Order updated' ), { - type: 'snackbar', - } ); - onActionPerformed?.( items ); - } catch ( error ) { - const errorMessage = - error.message && error.code !== 'unknown_error' - ? error.message - : __( 'An error occurred while updating the order' ); - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); - } - } - const isSaveDisabled = ! isItemValid( item, fields, formOrderAction ); - return ( -
- -
- { __( - 'Determines the order of pages. Pages with the same order value are sorted alphabetically. Negative order values are supported.' - ) } -
- - - - - -
-
- ); -} - -function useReorderPagesAction( postType ) { - const supportsPageAttributes = useSelect( - ( select ) => { - const { getPostType } = select( coreStore ); - const postTypeObject = getPostType( postType ); - - return !! postTypeObject?.supports?.[ 'page-attributes' ]; - }, - [ postType ] - ); - - return useMemo( - () => - supportsPageAttributes && { - id: 'order-pages', - label: __( 'Order' ), - isEligible( { status } ) { - return status !== 'trash'; - }, - RenderModal: ReorderModal, - }, - [ supportsPageAttributes ] - ); -} - const useDuplicatePostAction = ( postType ) => { const userCanCreatePost = useSelect( ( select ) => { @@ -595,7 +487,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) { }, [ registerPostTypeActions, postType ] ); const duplicatePostAction = useDuplicatePostAction( postType ); - const reorderPagesAction = useReorderPagesAction( postType ); const isTemplateOrTemplatePart = [ TEMPLATE_POST_TYPE, TEMPLATE_PART_POST_TYPE, @@ -622,7 +513,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) { duplicateTemplatePartAction, isPattern && userCanCreatePostType && duplicatePatternAction, supportsTitle && renamePostAction, - reorderPagesAction, ...defaultActions, ].filter( Boolean ); // Filter actions based on provided context. If not provided @@ -693,7 +583,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) { isPattern, postTypeObject?.viewable, duplicatePostAction, - reorderPagesAction, onActionPerformed, isLoaded, supportsRevisions, diff --git a/packages/editor/src/dataviews/actions/reorder-page.native.tsx b/packages/editor/src/dataviews/actions/reorder-page.native.tsx new file mode 100644 index 0000000000000..61e4733b6c6bd --- /dev/null +++ b/packages/editor/src/dataviews/actions/reorder-page.native.tsx @@ -0,0 +1,3 @@ +const reorderPage = undefined; + +export default reorderPage; diff --git a/packages/editor/src/dataviews/actions/reorder-page.tsx b/packages/editor/src/dataviews/actions/reorder-page.tsx new file mode 100644 index 0000000000000..9193904f0f912 --- /dev/null +++ b/packages/editor/src/dataviews/actions/reorder-page.tsx @@ -0,0 +1,120 @@ +/** + * WordPress dependencies + */ +import { useDispatch } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { __ } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; +import { useState } from '@wordpress/element'; +import { DataForm, isItemValid } from '@wordpress/dataviews'; +import { + Button, + __experimentalHStack as HStack, + __experimentalVStack as VStack, +} from '@wordpress/components'; +import type { Action, RenderModalProps } from '@wordpress/dataviews'; + +/** + * Internal dependencies + */ +import type { CoreDataError, PostWithPageAttributesSupport } from '../types'; +import { orderField } from '../fields'; + +const fields = [ orderField ]; +const formOrderAction = { + fields: [ 'menu_order' ], +}; + +function ReorderModal( { + items, + closeModal, + onActionPerformed, +}: RenderModalProps< PostWithPageAttributesSupport > ) { + const [ item, setItem ] = useState( items[ 0 ] ); + const orderInput = item.menu_order; + const { editEntityRecord, saveEditedEntityRecord } = + useDispatch( coreStore ); + const { createSuccessNotice, createErrorNotice } = + useDispatch( noticesStore ); + + async function onOrder( event: React.FormEvent ) { + event.preventDefault(); + + if ( ! isItemValid( item, fields, formOrderAction ) ) { + return; + } + + try { + await editEntityRecord( 'postType', item.type, item.id, { + menu_order: orderInput, + } ); + closeModal?.(); + // Persist edited entity. + await saveEditedEntityRecord( 'postType', item.type, item.id, { + throwOnError: true, + } ); + createSuccessNotice( __( 'Order updated.' ), { + type: 'snackbar', + } ); + onActionPerformed?.( items ); + } catch ( error ) { + const typedError = error as CoreDataError; + const errorMessage = + typedError.message && typedError.code !== 'unknown_error' + ? typedError.message + : __( 'An error occurred while updating the order' ); + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } + } + const isSaveDisabled = ! isItemValid( item, fields, formOrderAction ); + return ( +
+ +
+ { __( + 'Determines the order of pages. Pages with the same order value are sorted alphabetically. Negative order values are supported.' + ) } +
+ + + + + +
+
+ ); +} + +const reorderPage: Action< PostWithPageAttributesSupport > = { + id: 'order-pages', + label: __( 'Order' ), + isEligible( { status } ) { + return status !== 'trash'; + }, + RenderModal: ReorderModal, +}; + +export default reorderPage; diff --git a/packages/editor/src/dataviews/fields/index.ts b/packages/editor/src/dataviews/fields/index.ts new file mode 100644 index 0000000000000..ea30d15dab600 --- /dev/null +++ b/packages/editor/src/dataviews/fields/index.ts @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import type { Field } from '@wordpress/dataviews'; + +/** + * Internal dependencies + */ +import type { BasePost, PostWithPageAttributesSupport } from '../types'; + +export const titleField: Field< BasePost > = { + type: 'text', + id: 'title', + label: __( 'Title' ), + placeholder: __( 'No title' ), + getValue: ( { item } ) => item.title, +}; + +export const orderField: Field< PostWithPageAttributesSupport > = { + type: 'integer', + id: 'menu_order', + label: __( 'Order' ), + description: __( 'Determines the order of pages.' ), +}; diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts index 745cc0ad82e93..d5e12e298039a 100644 --- a/packages/editor/src/dataviews/store/private-actions.ts +++ b/packages/editor/src/dataviews/store/private-actions.ts @@ -14,6 +14,7 @@ import resetPost from '../actions/reset-post'; import trashPost from '../actions/trash-post'; import permanentlyDeletePost from '../actions/permanently-delete-post'; import restorePost from '../actions/restore-post'; +import reorderPage from '../actions/reorder-page'; import type { PostType } from '../types'; import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; @@ -73,6 +74,9 @@ export const registerPostTypeActions = .getPostType( postType ) ) as PostType; const actions = [ + postTypeConfig?.supports?.[ 'page-attributes' ] + ? reorderPage + : undefined, postTypeConfig.slug === 'wp_block' ? exportPattern : undefined, resetPost, restorePost, diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts index 47f11c88bfb97..0c31bef195eb9 100644 --- a/packages/editor/src/dataviews/types.ts +++ b/packages/editor/src/dataviews/types.ts @@ -27,6 +27,10 @@ export interface Pattern extends BasePost { wp_pattern_sync_status: string; } +export interface PostWithPageAttributesSupport extends BasePost { + menu_order: number; +} + export type Post = TemplateOrTemplatePart | Pattern | BasePost; export type PostWithPermissions = Post & { @@ -38,6 +42,9 @@ export type PostWithPermissions = Post & { export interface PostType { slug: string; + supports?: { + 'page-attributes'?: boolean; + }; } // Will be unnecessary after typescript 5.0 upgrade.