From 53b749c128895c7f21326dcfa8623c1cc287424b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 18 Jun 2024 10:59:16 +0200 Subject: [PATCH 1/2] DataViews: Register the permanentlyDelete action like any third-party action --- .../src/components/post-actions/actions.js | 93 +------------- .../dataviews/actions/delete-permanently.ts | 120 ++++++++++++++++++ .../editor/src/dataviews/actions/index.js | 26 ++++ .../editor/src/dataviews/actions/utils.ts | 16 +++ .../src/dataviews/store/private-selectors.ts | 19 ++- packages/editor/src/dataviews/types.ts | 17 +++ .../src/store/{constants.js => constants.ts} | 0 7 files changed, 194 insertions(+), 97 deletions(-) create mode 100644 packages/editor/src/dataviews/actions/delete-permanently.ts create mode 100644 packages/editor/src/dataviews/actions/index.js create mode 100644 packages/editor/src/dataviews/actions/utils.ts create mode 100644 packages/editor/src/dataviews/types.ts rename packages/editor/src/store/{constants.js => constants.ts} (100%) diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 5cb208e23e7867..91992b81c149db 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -33,6 +33,7 @@ import { unlock } from '../../lock-unlock'; import isTemplateRevertable from '../../store/utils/is-template-revertable'; import { exportPatternAsJSONAction } from './export-pattern-action'; import { CreateTemplatePartModalContents } from '../create-template-part-modal'; +import useDefaultActions from '../../dataviews/actions'; // Patterns. const { PATTERN_TYPES, CreatePatternModalContents, useDuplicatePatternProps } = @@ -307,96 +308,6 @@ const trashPostAction = { }, }; -const permanentlyDeletePostAction = { - id: 'permanently-delete', - label: __( 'Permanently delete' ), - supportsBulk: true, - isEligible( { status } ) { - return status === 'trash'; - }, - async callback( posts, { registry } ) { - const { createSuccessNotice, createErrorNotice } = - registry.dispatch( noticesStore ); - const { deleteEntityRecord } = registry.dispatch( coreStore ); - const promiseResult = await Promise.allSettled( - posts.map( ( post ) => { - return deleteEntityRecord( - 'postType', - post.type, - post.id, - { force: true }, - { throwOnError: true } - ); - } ) - ); - // If all the promises were fulfilled with success. - if ( promiseResult.every( ( { status } ) => status === 'fulfilled' ) ) { - let successMessage; - if ( promiseResult.length === 1 ) { - successMessage = sprintf( - /* translators: The posts's title. */ - __( '"%s" permanently deleted.' ), - getItemTitle( posts[ 0 ] ) - ); - } else { - successMessage = __( 'The posts were permanently deleted.' ); - } - createSuccessNotice( successMessage, { - type: 'snackbar', - id: 'permanently-delete-post-action', - } ); - } else { - // If there was at lease one failure. - let errorMessage; - // If we were trying to permanently delete a single post. - if ( promiseResult.length === 1 ) { - if ( promiseResult[ 0 ].reason?.message ) { - errorMessage = promiseResult[ 0 ].reason.message; - } else { - errorMessage = __( - 'An error occurred while permanently deleting the post.' - ); - } - // If we were trying to permanently delete multiple posts - } else { - const errorMessages = new Set(); - const failedPromises = promiseResult.filter( - ( { status } ) => status === 'rejected' - ); - for ( const failedPromise of failedPromises ) { - if ( failedPromise.reason?.message ) { - errorMessages.add( failedPromise.reason.message ); - } - } - if ( errorMessages.size === 0 ) { - errorMessage = __( - 'An error occurred while permanently deleting the posts.' - ); - } else if ( errorMessages.size === 1 ) { - errorMessage = sprintf( - /* translators: %s: an error message */ - __( - 'An error occurred while permanently deleting the posts: %s' - ), - [ ...errorMessages ][ 0 ] - ); - } else { - errorMessage = sprintf( - /* translators: %s: a list of comma separated error messages */ - __( - 'Some errors occurred while permanently deleting the posts: %s' - ), - [ ...errorMessages ].join( ',' ) - ); - } - } - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); - } - }, -}; - const restorePostAction = { id: 'restore', label: __( 'Restore' ), @@ -1007,6 +918,7 @@ export const duplicateTemplatePartAction = { }; export function usePostActions( { postType, onActionPerformed, context } ) { + useDefaultActions(); const { defaultActions, postTypeObject } = useSelect( ( select ) => { const { getPostType } = select( coreStore ); @@ -1049,7 +961,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) { isTemplateOrTemplatePart || isPattern ? deletePostAction : trashPostAction, - ! isTemplateOrTemplatePart && permanentlyDeletePostAction, ...defaultActions, ].filter( Boolean ); // Filter actions based on provided context. If not provided diff --git a/packages/editor/src/dataviews/actions/delete-permanently.ts b/packages/editor/src/dataviews/actions/delete-permanently.ts new file mode 100644 index 00000000000000..9933939ec33bc5 --- /dev/null +++ b/packages/editor/src/dataviews/actions/delete-permanently.ts @@ -0,0 +1,120 @@ +/** + * WordPress dependencies + */ +import { store as coreStore } from '@wordpress/core-data'; +import { store as noticesStore } from '@wordpress/notices'; +import { __, sprintf } from '@wordpress/i18n'; +import type { Action } from '@wordpress/dataviews'; + +/** + * Internal dependencies + */ +import { getItemTitle } from './utils'; +import type { Post } from '../types'; +import { + TEMPLATE_POST_TYPE, + TEMPLATE_PART_POST_TYPE, +} from '../../store/constants'; + +type CoreDataError = { + reason: { message?: string }; +}; + +const permanentlyDeletePostAction: Action< Post > = { + id: 'permanently-delete', + label: __( 'Permanently delete' ), + supportsBulk: true, + isEligible( { status, type }: Post ) { + const isTemplateOrTemplatePart = [ + TEMPLATE_POST_TYPE, + TEMPLATE_PART_POST_TYPE, + ].includes( type ); + + return ! isTemplateOrTemplatePart && status === 'trash'; + }, + async callback( posts, { registry } ) { + const { createSuccessNotice, createErrorNotice } = + registry.dispatch( noticesStore ); + const { deleteEntityRecord } = registry.dispatch( coreStore ); + const promiseResult = await Promise.allSettled( + posts.map( ( post ) => { + return deleteEntityRecord( + 'postType', + post.type, + post.id, + { force: true }, + { throwOnError: true } + ); + } ) + ); + // If all the promises were fulfilled with success. + if ( promiseResult.every( ( { status } ) => status === 'fulfilled' ) ) { + let successMessage; + if ( promiseResult.length === 1 ) { + successMessage = sprintf( + /* translators: The posts's title. */ + __( '"%s" permanently deleted.' ), + getItemTitle( posts[ 0 ] ) + ); + } else { + successMessage = __( 'The posts were permanently deleted.' ); + } + createSuccessNotice( successMessage, { + type: 'snackbar', + id: 'permanently-delete-post-action', + } ); + } else { + // If there was at lease one failure. + let errorMessage; + // If we were trying to permanently delete a single post. + if ( promiseResult.length === 1 ) { + const result = promiseResult[ 0 ] as CoreDataError; + if ( result.reason?.message ) { + errorMessage = result.reason.message; + } else { + errorMessage = __( + 'An error occurred while permanently deleting the post.' + ); + } + // If we were trying to permanently delete multiple posts + } else { + const errorMessages = new Set(); + const failedPromises = promiseResult.filter( + ( { status } ) => status === 'rejected' + ); + for ( const failedPromise of failedPromises ) { + const result = failedPromise as CoreDataError; + if ( result.reason?.message ) { + errorMessages.add( result.reason.message ); + } + } + if ( errorMessages.size === 0 ) { + errorMessage = __( + 'An error occurred while permanently deleting the posts.' + ); + } else if ( errorMessages.size === 1 ) { + errorMessage = sprintf( + /* translators: %s: an error message */ + __( + 'An error occurred while permanently deleting the posts: %s' + ), + [ ...errorMessages ][ 0 ] + ); + } else { + errorMessage = sprintf( + /* translators: %s: a list of comma separated error messages */ + __( + 'Some errors occurred while permanently deleting the posts: %s' + ), + [ ...errorMessages ].join( ',' ) + ); + } + } + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } + }, +}; + +export default permanentlyDeletePostAction; diff --git a/packages/editor/src/dataviews/actions/index.js b/packages/editor/src/dataviews/actions/index.js new file mode 100644 index 00000000000000..e67f2cb1d60879 --- /dev/null +++ b/packages/editor/src/dataviews/actions/index.js @@ -0,0 +1,26 @@ +/** + * WordPress dependencies + */ +import { useEffect } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import deletePermanently from './delete-permanently'; +import { unlock } from '../../lock-unlock'; +import { store as editorStore } from '../../store'; + +export default function useDefaultActions() { + const { registerEntityAction, unregisterEntityAction } = unlock( + useDispatch( editorStore ) + ); + + useEffect( () => { + registerEntityAction( 'postType', '*', deletePermanently ); + + return () => { + unregisterEntityAction( 'postType', '*', deletePermanently.id ); + }; + }, [ registerEntityAction, unregisterEntityAction ] ); +} diff --git a/packages/editor/src/dataviews/actions/utils.ts b/packages/editor/src/dataviews/actions/utils.ts new file mode 100644 index 00000000000000..c0100135908de3 --- /dev/null +++ b/packages/editor/src/dataviews/actions/utils.ts @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { decodeEntities } from '@wordpress/html-entities'; + +/** + * Internal dependencies + */ +import type { Post } from '../types'; + +export function getItemTitle( item: Post ) { + if ( typeof item.title === 'string' ) { + return decodeEntities( item.title ); + } + return decodeEntities( item.title?.rendered || '' ); +} diff --git a/packages/editor/src/dataviews/store/private-selectors.ts b/packages/editor/src/dataviews/store/private-selectors.ts index b2f60d7b0f33e1..bbe3d7ca95c7c1 100644 --- a/packages/editor/src/dataviews/store/private-selectors.ts +++ b/packages/editor/src/dataviews/store/private-selectors.ts @@ -1,15 +1,22 @@ /** * WordPress dependencies */ -import type { Action } from '@wordpress/dataviews'; +import { createSelector } from '@wordpress/data'; /** * Internal dependencies */ import type { State } from './reducer'; -const EMPTY_ARRAY: Action< any >[] = []; - -export function getEntityActions( state: State, kind: string, name: string ) { - return state.actions[ kind ]?.[ name ] ?? EMPTY_ARRAY; -} +export const getEntityActions = createSelector( + ( state: State, kind: string, name: string ) => { + return [ + ...( state.actions[ kind ]?.[ name ] ?? [] ), + ...( state.actions[ kind ]?.[ '*' ] ?? [] ), + ]; + }, + ( state: State, kind: string, name: string ) => [ + state.actions[ kind ]?.[ name ], + state.actions[ kind ]?.[ '*' ], + ] +); diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts new file mode 100644 index 00000000000000..8ed2d87d93b0d1 --- /dev/null +++ b/packages/editor/src/dataviews/types.ts @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import type { AnyItem } from '@wordpress/dataviews'; + +type PostStatus = + | 'published' + | 'draft' + | 'pending' + | 'private' + | 'future' + | 'auto-draft' + | 'trash'; + +export interface Post extends AnyItem { + status?: PostStatus; +} diff --git a/packages/editor/src/store/constants.js b/packages/editor/src/store/constants.ts similarity index 100% rename from packages/editor/src/store/constants.js rename to packages/editor/src/store/constants.ts From ee56139c9733c0cc190e278a6bbc831a10d6c4f8 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 21 Jun 2024 09:17:07 +0200 Subject: [PATCH 2/2] Fix types --- packages/dataviews/src/types.ts | 2 +- packages/edit-site/src/components/editor/style.scss | 2 ++ packages/editor/src/dataviews/types.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 8c1819b3a7c674..d174f0cc80f5ce 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -352,7 +352,7 @@ export interface ActionModal< Item extends AnyItem > } export interface ActionButton< Item extends AnyItem > - extends ActionBase< AnyItem > { + extends ActionBase< Item > { /** * The callback to execute when the action is triggered. */ diff --git a/packages/edit-site/src/components/editor/style.scss b/packages/edit-site/src/components/editor/style.scss index b157057062c9d1..4832485d343989 100644 --- a/packages/edit-site/src/components/editor/style.scss +++ b/packages/edit-site/src/components/editor/style.scss @@ -1,3 +1,5 @@ + + .edit-site-editor__editor-interface { opacity: 1; transition: opacity 0.1s ease-out; diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts index 8ed2d87d93b0d1..9f866db87235e2 100644 --- a/packages/editor/src/dataviews/types.ts +++ b/packages/editor/src/dataviews/types.ts @@ -13,5 +13,5 @@ type PostStatus = | 'trash'; export interface Post extends AnyItem { - status?: PostStatus; + status: PostStatus; }