Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DataViews Extensibility: Add a hook to allow third-party scripts to register/unregister post type actions #64138

Merged
merged 5 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/edit-post/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { unlock } from './lock-unlock';

const {
BackButton: __experimentalMainDashboardButton,
registerDefaultActions,
registerCoreBlockBindingsSources,
bootstrapBlockBindingsSourcesFromServer,
} = unlock( editorPrivateApis );
Expand Down Expand Up @@ -97,7 +96,6 @@ 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
2 changes: 0 additions & 2 deletions packages/edit-site/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { unlock } from './lock-unlock';
import App from './components/app';

const {
registerDefaultActions,
registerCoreBlockBindingsSources,
bootstrapBlockBindingsSourcesFromServer,
} = unlock( editorPrivateApis );
Expand Down Expand Up @@ -59,7 +58,6 @@ 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
6 changes: 5 additions & 1 deletion packages/editor/src/components/post-actions/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { decodeEntities } from '@wordpress/html-entities';
import { store as coreStore } from '@wordpress/core-data';
import { __, sprintf, _x } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
import { useMemo, useState } from '@wordpress/element';
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';
Expand Down Expand Up @@ -589,6 +589,10 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
},
[ postType ]
);
const { registerPostTypeActions } = unlock( useDispatch( editorStore ) );
useEffect( () => {
registerPostTypeActions( postType );
}, [ registerPostTypeActions, postType ] );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love these kinds of effects tbh. Not sure what the alternative is, I guess filtered context wouldn't work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for me a good thing: Only register the actions and run the filter when needed (when using usePostAction hook), that said, I agree that the code is weird, but I don't know of another pattern that provides the same benefits.


const duplicatePostAction = useDuplicatePostAction( postType );
const reorderPagesAction = useReorderPagesAction( postType );
Expand Down
31 changes: 0 additions & 31 deletions packages/editor/src/dataviews/actions/index.ts

This file was deleted.

68 changes: 68 additions & 0 deletions packages/editor/src/dataviews/store/private-actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
/**
* WordPress dependencies
*/
import { store as coreStore } from '@wordpress/core-data';
import type { Action } from '@wordpress/dataviews';
import { doAction } from '@wordpress/hooks';

/**
* Internal dependencies
*/
import deletePost from '../actions/delete-post';
import exportPattern from '../actions/export-pattern';
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 type { PostType } from '../types';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';

export function registerEntityAction< Item >(
kind: string,
Expand All @@ -28,3 +43,56 @@ export function unregisterEntityAction(
actionId,
};
}

export function setIsReady( kind: string, name: string ) {
return {
type: 'SET_IS_READY' as const,
kind,
name,
};
}

export const registerPostTypeActions =
( postType: string ) =>
async ( { registry }: { registry: any } ) => {
const isReady = unlock( registry.select( editorStore ) ).isEntityReady(
'postType',
postType
);
if ( isReady ) {
return;
}

unlock( registry.dispatch( editorStore ) ).setIsReady(
'postType',
postType
);

const postTypeConfig = ( await registry
.resolveSelect( coreStore )
.getPostType( postType ) ) as PostType;

const actions = [
postTypeConfig.slug === 'wp_block' ? exportPattern : undefined,
resetPost,
restorePost,
deletePost,
trashPost,
permanentlyDeletePost,
];

registry.batch( () => {
actions.forEach( ( action ) => {
if ( action === undefined ) {
return;
}
unlock( registry.dispatch( editorStore ) ).registerEntityAction(
'postType',
postType,
action
);
} );
} );

doAction( 'core.registerPostTypeActions', Promise.resolve(), postType );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could our action registration use the same hook?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see two sides here:

  • Core code is not special and should use the same thing.
  • Adds complexity: figuring out when to register the action, which package and also no special treatment for performance...

};
26 changes: 9 additions & 17 deletions packages/editor/src/dataviews/store/private-selectors.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
/**
* WordPress dependencies
*/
import { createSelector } from '@wordpress/data';

/**
* Internal dependencies
*/
import type { State } from './reducer';

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 ]?.[ '*' ],
]
);
const EMPTY_ARRAY = [] as [];

export function getEntityActions( state: State, kind: string, name: string ) {
return state.actions[ kind ]?.[ name ] ?? EMPTY_ARRAY;
}

export function isEntityReady( state: State, kind: string, name: string ) {
return state.isReady[ kind ]?.[ name ];
}
21 changes: 20 additions & 1 deletion packages/editor/src/dataviews/store/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,31 @@ import type { Action } from '@wordpress/dataviews';

type ReduxAction =
| ReturnType< typeof import('./private-actions').registerEntityAction >
| ReturnType< typeof import('./private-actions').unregisterEntityAction >;
| ReturnType< typeof import('./private-actions').unregisterEntityAction >
| ReturnType< typeof import('./private-actions').setIsReady >;

export type ActionState = Record< string, Record< string, Action< any >[] > >;
export type ReadyState = Record< string, Record< string, boolean > >;
export type State = {
actions: ActionState;
isReady: ReadyState;
};

function isReady( state: ReadyState = {}, action: ReduxAction ) {
switch ( action.type ) {
case 'SET_IS_READY':
return {
...state,
[ action.kind ]: {
...state[ action.kind ],
[ action.name ]: true,
},
};
}

return state;
}

function actions( state: ActionState = {}, action: ReduxAction ) {
switch ( action.type ) {
case 'REGISTER_ENTITY_ACTION':
Expand Down Expand Up @@ -48,4 +66,5 @@ function actions( state: ActionState = {}, action: ReduxAction ) {

export default combineReducers( {
actions,
isReady,
} );
4 changes: 4 additions & 0 deletions packages/editor/src/dataviews/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,9 @@ export type PostWithPermissions = Post & {
};
};

export interface PostType {
slug: string;
}

// Will be unnecessary after typescript 5.0 upgrade.
export type CoreDataError = { message?: string; code?: string };
2 changes: 0 additions & 2 deletions packages/editor/src/private-apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
mergeBaseAndUserConfigs,
GlobalStylesProvider,
} from './components/global-styles-provider';
import registerDefaultActions from './dataviews/actions';
import {
registerCoreBlockBindingsSources,
bootstrapBlockBindingsSourcesFromServer,
Expand All @@ -46,7 +45,6 @@ lock( privateApis, {
ToolsMoreMenuGroup,
ViewMoreMenuGroup,
ResizableEditor,
registerDefaultActions,
registerCoreBlockBindingsSources,
bootstrapBlockBindingsSourcesFromServer,

Expand Down
9 changes: 8 additions & 1 deletion packages/editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import {
getCurrentPost,
__experimentalGetDefaultTemplatePartAreas,
} from './selectors';
import { getEntityActions as _getEntityActions } from '../dataviews/store/private-selectors';
import {
getEntityActions as _getEntityActions,
isEntityReady as _isEntityReady,
} from '../dataviews/store/private-selectors';

const EMPTY_INSERTION_POINT = {
rootClientId: undefined,
Expand Down Expand Up @@ -161,6 +164,10 @@ export function getEntityActions( state, ...args ) {
return _getEntityActions( state.dataviews, ...args );
}

export function isEntityReady( state, ...args ) {
return _isEntityReady( state.dataviews, ...args );
}

/**
* Similar to getBlocksByName in @wordpress/block-editor, but only returns the top-most
* blocks that aren't descendants of the query block.
Expand Down
Loading