From cf614c889a231c241f6ce7fc9c65eaf3e8ee23c7 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 1 Jul 2024 13:31:55 +0200 Subject: [PATCH] DataViews: Register the deletePost action like any third-party action. (#62913) Co-authored-by: youknowriad Co-authored-by: sirreal Co-authored-by: jorgefilipecosta --- packages/core-data/src/entity-provider.js | 4 +- packages/edit-post/src/index.js | 7 +- .../src/components/page-pages/index.js | 1 - packages/edit-site/src/index.js | 9 +- .../src/components/post-actions/actions.js | 81 +------------ .../src/dataviews/actions/delete-post.tsx | 107 ++++++++++++++++++ .../editor/src/dataviews/actions/index.ts | 20 ++++ .../editor/src/dataviews/actions/utils.ts | 50 ++++++++ .../src/dataviews/store/private-selectors.ts | 19 +++- packages/editor/src/dataviews/types.ts | 21 ++++ packages/editor/src/private-apis.js | 2 + .../src/store/{constants.js => constants.ts} | 0 packages/editor/tsconfig.json | 5 +- patches/postcss-urlrebase+1.3.0.patch | 11 ++ 14 files changed, 244 insertions(+), 93 deletions(-) create mode 100644 packages/editor/src/dataviews/actions/delete-post.tsx create mode 100644 packages/editor/src/dataviews/actions/index.ts 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%) create mode 100644 patches/postcss-urlrebase+1.3.0.patch diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index a2324c06e4dbc1..bc1fe29759950b 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -16,8 +16,6 @@ import { parse, __unstableSerializeAndClean } from '@wordpress/blocks'; import { STORE_NAME } from './name'; import { updateFootnotesFromMeta } from './footnotes'; -/** @typedef {import('@wordpress/blocks').WPBlock} WPBlock */ - const EMPTY_ARRAY = []; const EntityContext = createContext( {} ); @@ -133,7 +131,7 @@ const parsedBlocksCache = new WeakMap(); * @param {Object} options * @param {string} [options.id] An entity ID to use instead of the context-provided one. * - * @return {[WPBlock[], Function, Function]} The block array and setters. + * @return {[unknown[], Function, Function]} The block array and setters. */ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { const providerId = useEntityId( kind, name ); diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 152afc294f4283..0b4cf883d07809 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -25,8 +25,10 @@ import { import Layout from './components/layout'; import { unlock } from './lock-unlock'; -const { BackButton: __experimentalMainDashboardButton } = - unlock( editorPrivateApis ); +const { + BackButton: __experimentalMainDashboardButton, + registerDefaultActions, +} = unlock( editorPrivateApis ); /** * Initializes and returns an instance of Editor. @@ -91,6 +93,7 @@ export function initializeEditor( enableFSEBlocks: settings.__unstableEnableFullSiteEditingBlocks, } ); } + registerDefaultActions(); // Show a console log warning if the browser is not in Standards rendering mode. const documentMode = diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js index f9888be6b27cad..01c056cdca33d8 100644 --- a/packages/edit-site/src/components/page-pages/index.js +++ b/packages/edit-site/src/components/page-pages/index.js @@ -482,7 +482,6 @@ export default function PagePages() { ], [ authors, view.type, frontPageId, postsPageId ] ); - const postTypeActions = usePostActions( { postType: 'page', context: 'list', diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index 3e8c637a493de8..14f5bb6be650c5 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -10,7 +10,10 @@ import { import { dispatch } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; import { createRoot, StrictMode } from '@wordpress/element'; -import { store as editorStore } from '@wordpress/editor'; +import { + store as editorStore, + privateApis as editorPrivateApis, +} from '@wordpress/editor'; import { store as preferencesStore } from '@wordpress/preferences'; import { registerLegacyWidgetBlock, @@ -22,8 +25,11 @@ import { */ import './hooks'; import { store as editSiteStore } from './store'; +import { unlock } from './lock-unlock'; import App from './components/app'; +const { registerDefaultActions } = unlock( editorPrivateApis ); + /** * Initializes the site editor screen. * @@ -47,6 +53,7 @@ export function initializeEditor( id, settings ) { enableFSEBlocks: true, } ); } + registerDefaultActions(); // We dispatch actions and update the store synchronously before rendering // so that we won't trigger unnecessary re-renders with useEffect. diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 89ac77a6945c4d..60fc718279afb5 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -73,81 +73,6 @@ function getItemTitle( item ) { return decodeEntities( item.title?.rendered || '' ); } -// This action is used for templates, patterns and template parts. -// Every other post type uses the similar `trashPostAction` which -// moves the post to trash. -const deletePostAction = { - id: 'delete-post', - label: __( 'Delete' ), - isPrimary: true, - icon: trash, - isEligible( post ) { - if ( - [ TEMPLATE_POST_TYPE, TEMPLATE_PART_POST_TYPE ].includes( - post.type - ) - ) { - return isTemplateRemovable( post ); - } - // We can only remove user patterns. - return post.type === PATTERN_TYPES.user; - }, - supportsBulk: true, - hideModalHeader: true, - RenderModal: ( { items, closeModal, onActionPerformed } ) => { - const [ isBusy, setIsBusy ] = useState( false ); - const { removeTemplates } = unlock( useDispatch( editorStore ) ); - return ( - - - { items.length > 1 - ? sprintf( - // translators: %d: number of items to delete. - _n( - 'Delete %d item?', - 'Delete %d items?', - items.length - ), - items.length - ) - : sprintf( - // translators: %s: The template or template part's titles - __( 'Delete "%s"?' ), - getItemTitle( items[ 0 ] ) - ) } - - - - - - - ); - }, -}; - const trashPostAction = { id: 'move-to-trash', label: __( 'Move to Trash' ), @@ -1124,9 +1049,9 @@ export function usePostActions( { postType, onActionPerformed, context } ) { isTemplateOrTemplatePart ? resetTemplateAction : restorePostActionForPostType, - isTemplateOrTemplatePart || isPattern - ? deletePostAction - : trashPostActionForPostType, + ! isTemplateOrTemplatePart && + ! isPattern && + trashPostActionForPostType, ! isTemplateOrTemplatePart && permanentlyDeletePostActionForPostType, ...defaultActions, diff --git a/packages/editor/src/dataviews/actions/delete-post.tsx b/packages/editor/src/dataviews/actions/delete-post.tsx new file mode 100644 index 00000000000000..e5810e32fde69a --- /dev/null +++ b/packages/editor/src/dataviews/actions/delete-post.tsx @@ -0,0 +1,107 @@ +/** + * WordPress dependencies + */ +import { trash } from '@wordpress/icons'; +import { useDispatch } from '@wordpress/data'; +import { __, _n, sprintf, _x } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; +import { + Button, + __experimentalText as Text, + __experimentalHStack as HStack, + __experimentalVStack as VStack, +} from '@wordpress/components'; +// @ts-ignore +import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; +import type { Action } from '@wordpress/dataviews'; +import type { StoreDescriptor } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { + isTemplateRemovable, + getItemTitle, + isTemplateOrTemplatePart, +} from './utils'; +// @ts-ignore +import { store as editorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; +import type { Post } from '../types'; + +const { PATTERN_TYPES } = unlock( patternsPrivateApis ); + +// This action is used for templates, patterns and template parts. +// Every other post type uses the similar `trashPostAction` which +// moves the post to trash. +const deletePostAction: Action< Post > = { + id: 'delete-post', + label: __( 'Delete' ), + isPrimary: true, + icon: trash, + isEligible( post ) { + if ( isTemplateOrTemplatePart( post ) ) { + return isTemplateRemovable( post ); + } + // We can only remove user patterns. + return post.type === PATTERN_TYPES.user; + }, + supportsBulk: true, + hideModalHeader: true, + RenderModal: ( { items, closeModal, onActionPerformed } ) => { + const [ isBusy, setIsBusy ] = useState( false ); + const { removeTemplates } = unlock( + useDispatch( editorStore as StoreDescriptor ) + ); + return ( + + + { items.length > 1 + ? sprintf( + // translators: %d: number of items to delete. + _n( + 'Delete %d item?', + 'Delete %d items?', + items.length + ), + items.length + ) + : sprintf( + // translators: %s: The template or template part's titles + __( 'Delete "%s"?' ), + getItemTitle( items[ 0 ] ) + ) } + + + + + + + ); + }, +}; + +export default deletePostAction; diff --git a/packages/editor/src/dataviews/actions/index.ts b/packages/editor/src/dataviews/actions/index.ts new file mode 100644 index 00000000000000..87589d68262f3e --- /dev/null +++ b/packages/editor/src/dataviews/actions/index.ts @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { type StoreDescriptor, dispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import deletePost from './delete-post'; +// @ts-ignore +import { store as editorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; + +export default function registerDefaultActions() { + const { registerEntityAction } = unlock( + dispatch( editorStore as StoreDescriptor ) + ); + + registerEntityAction( 'postType', '*', deletePost ); +} diff --git a/packages/editor/src/dataviews/actions/utils.ts b/packages/editor/src/dataviews/actions/utils.ts new file mode 100644 index 00000000000000..14da130789b08c --- /dev/null +++ b/packages/editor/src/dataviews/actions/utils.ts @@ -0,0 +1,50 @@ +/** + * WordPress dependencies + */ +import { decodeEntities } from '@wordpress/html-entities'; + +/** + * Internal dependencies + */ +import { + TEMPLATE_ORIGINS, + TEMPLATE_PART_POST_TYPE, + TEMPLATE_POST_TYPE, +} from '../../store/constants'; + +import type { Post, TemplateOrTemplatePart } from '../types'; + +export function isTemplateOrTemplatePart( + p: Post +): p is TemplateOrTemplatePart { + return p.type === TEMPLATE_POST_TYPE || p.type === TEMPLATE_PART_POST_TYPE; +} + +export function getItemTitle( item: Post ) { + if ( typeof item.title === 'string' ) { + return decodeEntities( item.title ); + } + return decodeEntities( item.title?.rendered || '' ); +} + +/** + * Check if a template is removable. + * + * @param template The template entity to check. + * @return Whether the template is removable. + */ +export function isTemplateRemovable( template: TemplateOrTemplatePart ) { + if ( ! template ) { + return false; + } + // In patterns list page we map the templates parts to a different object + // than the one returned from the endpoint. This is why we need to check for + // two props whether is custom or has a theme file. + return ( + [ template.source, template.source ].includes( + TEMPLATE_ORIGINS.custom + ) && + ! template.has_theme_file && + ! template?.has_theme_file + ); +} 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..c1771801d8ee37 --- /dev/null +++ b/packages/editor/src/dataviews/types.ts @@ -0,0 +1,21 @@ +type PostStatus = + | 'published' + | 'draft' + | 'pending' + | 'private' + | 'future' + | 'auto-draft' + | 'trash'; + +export interface BasePost { + status?: PostStatus; + title: string | { rendered: string }; + type: string; +} +export interface TemplateOrTemplatePart extends BasePost { + type: 'template' | 'template-part'; + source: string; + has_theme_file: boolean; +} + +export type Post = TemplateOrTemplatePart | BasePost; diff --git a/packages/editor/src/private-apis.js b/packages/editor/src/private-apis.js index 48e43e7737fec8..8eeb97375268c3 100644 --- a/packages/editor/src/private-apis.js +++ b/packages/editor/src/private-apis.js @@ -23,6 +23,7 @@ import { mergeBaseAndUserConfigs, GlobalStylesProvider, } from './components/global-styles-provider'; +import registerDefaultActions from './dataviews/actions'; const { store: interfaceStore, ...remainingInterfaceApis } = interfaceApis; @@ -41,6 +42,7 @@ lock( privateApis, { ToolsMoreMenuGroup, ViewMoreMenuGroup, ResizableEditor, + registerDefaultActions, // This is a temporary private API while we're updating the site editor to use EditorProvider. useBlockEditorSettings, 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 diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json index 1177a1040b117b..8dfdf2ba7f2d5c 100644 --- a/packages/editor/tsconfig.json +++ b/packages/editor/tsconfig.json @@ -3,7 +3,8 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "src", - "declarationDir": "build-types" + "declarationDir": "build-types", + "checkJs": false }, "references": [ { "path": "../a11y" }, @@ -32,5 +33,5 @@ { "path": "../warning" }, { "path": "../wordcount" } ], - "include": [ "src/**/*.ts", "src/**/*.tsx" ] + "include": [ "src" ] } diff --git a/patches/postcss-urlrebase+1.3.0.patch b/patches/postcss-urlrebase+1.3.0.patch new file mode 100644 index 00000000000000..3ba02c0e8f2187 --- /dev/null +++ b/patches/postcss-urlrebase+1.3.0.patch @@ -0,0 +1,11 @@ +diff --git a/node_modules/postcss-urlrebase/index.d.ts b/node_modules/postcss-urlrebase/index.d.ts +index 88b25d0..441ace4 100644 +--- a/node_modules/postcss-urlrebase/index.d.ts ++++ b/node_modules/postcss-urlrebase/index.d.ts +@@ -1,6 +1,5 @@ + declare module 'rebaseUrl' { + import { PluginCreator } from 'postcss'; +- import { URL } from 'url'; + + interface RebaseUrlOptions { + /**