Skip to content

Commit

Permalink
DataViews: Register the deletePost action like any third-party action. (
Browse files Browse the repository at this point in the history
WordPress#62913)

Co-authored-by: youknowriad <[email protected]>
Co-authored-by: sirreal <[email protected]>
Co-authored-by: jorgefilipecosta <[email protected]>
  • Loading branch information
4 people authored and carstingaxion committed Jul 18, 2024
1 parent d261d75 commit cf614c8
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 93 deletions.
4 changes: 1 addition & 3 deletions packages/core-data/src/entity-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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( {} );
Expand Down Expand Up @@ -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 );
Expand Down
7 changes: 5 additions & 2 deletions packages/edit-post/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 =
Expand Down
1 change: 0 additions & 1 deletion packages/edit-site/src/components/page-pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,6 @@ export default function PagePages() {
],
[ authors, view.type, frontPageId, postsPageId ]
);

const postTypeActions = usePostActions( {
postType: 'page',
context: 'list',
Expand Down
9 changes: 8 additions & 1 deletion packages/edit-site/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
*
Expand All @@ -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.
Expand Down
81 changes: 3 additions & 78 deletions packages/editor/src/components/post-actions/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<VStack spacing="5">
<Text>
{ 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 ] )
) }
</Text>
<HStack justify="right">
<Button
variant="tertiary"
onClick={ closeModal }
disabled={ isBusy }
__experimentalIsFocusable
>
{ __( 'Cancel' ) }
</Button>
<Button
variant="primary"
onClick={ async () => {
setIsBusy( true );
await removeTemplates( items, {
allowUndo: false,
} );
onActionPerformed?.( items );
setIsBusy( false );
closeModal();
} }
isBusy={ isBusy }
disabled={ isBusy }
__experimentalIsFocusable
>
{ __( 'Delete' ) }
</Button>
</HStack>
</VStack>
);
},
};

const trashPostAction = {
id: 'move-to-trash',
label: __( 'Move to Trash' ),
Expand Down Expand Up @@ -1124,9 +1049,9 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
isTemplateOrTemplatePart
? resetTemplateAction
: restorePostActionForPostType,
isTemplateOrTemplatePart || isPattern
? deletePostAction
: trashPostActionForPostType,
! isTemplateOrTemplatePart &&
! isPattern &&
trashPostActionForPostType,
! isTemplateOrTemplatePart &&
permanentlyDeletePostActionForPostType,
...defaultActions,
Expand Down
107 changes: 107 additions & 0 deletions packages/editor/src/dataviews/actions/delete-post.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<VStack spacing="5">
<Text>
{ 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 ] )
) }
</Text>
<HStack justify="right">
<Button
variant="tertiary"
onClick={ closeModal }
disabled={ isBusy }
__experimentalIsFocusable
>
{ __( 'Cancel' ) }
</Button>
<Button
variant="primary"
onClick={ async () => {
setIsBusy( true );
await removeTemplates( items, {
allowUndo: false,
} );
onActionPerformed?.( items );
setIsBusy( false );
closeModal?.();
} }
isBusy={ isBusy }
disabled={ isBusy }
__experimentalIsFocusable
>
{ __( 'Delete' ) }
</Button>
</HStack>
</VStack>
);
},
};

export default deletePostAction;
20 changes: 20 additions & 0 deletions packages/editor/src/dataviews/actions/index.ts
Original file line number Diff line number Diff line change
@@ -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 );
}
50 changes: 50 additions & 0 deletions packages/editor/src/dataviews/actions/utils.ts
Original file line number Diff line number Diff line change
@@ -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
);
}
19 changes: 13 additions & 6 deletions packages/editor/src/dataviews/store/private-selectors.ts
Original file line number Diff line number Diff line change
@@ -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 ]?.[ '*' ],
]
);
Loading

0 comments on commit cf614c8

Please sign in to comment.