Skip to content

Commit

Permalink
DataViews: allow register/unregister fields (#67175)
Browse files Browse the repository at this point in the history
Co-authored-by: oandregal <[email protected]>
Co-authored-by: youknowriad <[email protected]>
Co-authored-by: ntsekouras <[email protected]>
Co-authored-by: louwie17 <[email protected]>
  • Loading branch information
5 people authored Nov 22, 2024
1 parent b4c614f commit 96f6338
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 37 deletions.
24 changes: 24 additions & 0 deletions packages/editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1602,6 +1602,18 @@ _Parameters_
- _name_ `string`: Entity name.
- _config_ `Action`: Action configuration.

### registerEntityField

Registers a new DataViews field.

This is an experimental API and is subject to change. it's only available in the Gutenberg plugin for now.

_Parameters_

- _kind_ `string`: Entity kind.
- _name_ `string`: Entity name.
- _config_ `Field`: Field configuration.

### RichText

> **Deprecated** since 5.3, use `wp.blockEditor.RichText` instead.
Expand Down Expand Up @@ -1697,6 +1709,18 @@ _Parameters_
- _name_ `string`: Entity name.
- _actionId_ `string`: Action ID.

### unregisterEntityField

Unregisters a DataViews field.

This is an experimental API and is subject to change. it's only available in the Gutenberg plugin for now.

_Parameters_

- _kind_ `string`: Entity kind.
- _name_ `string`: Entity name.
- _fieldId_ `string`: Field ID.

### UnsavedChangesWarning

Warns the user if there are unsaved changes before leaving the editor. Compatible with Post Editor and Site Editor.
Expand Down
6 changes: 3 additions & 3 deletions packages/editor/src/components/post-actions/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
[ postType ]
);

const { registerPostTypeActions } = unlock( useDispatch( editorStore ) );
const { registerPostTypeSchema } = unlock( useDispatch( editorStore ) );
useEffect( () => {
registerPostTypeActions( postType );
}, [ registerPostTypeActions, postType ] );
registerPostTypeSchema( postType );
}, [ registerPostTypeSchema, postType ] );

return useMemo( () => {
// Filter actions based on provided context. If not provided
Expand Down
71 changes: 41 additions & 30 deletions packages/editor/src/components/post-fields/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';
import { useEffect, useMemo } from '@wordpress/element';
import { useEntityRecords } from '@wordpress/core-data';
import { useDispatch, useSelect } from '@wordpress/data';
import type { Field } from '@wordpress/dataviews';
import {
featuredImageField,
slugField,
parentField,
passwordField,
statusField,
commentStatusField,
titleField,
dateField,
authorField,
} from '@wordpress/fields';
import type { BasePostWithEmbeddedAuthor } from '@wordpress/fields';

/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';
import { store as editorStore } from '../../store';

interface UsePostFieldsReturn {
isLoading: boolean;
fields: Field< BasePostWithEmbeddedAuthor >[];
Expand All @@ -28,29 +24,44 @@ interface Author {
}

function usePostFields(): UsePostFieldsReturn {
const postType = 'page'; // TODO: this could be page or post (experimental).

const { registerPostTypeSchema } = unlock( useDispatch( editorStore ) );
useEffect( () => {
registerPostTypeSchema( postType );
}, [ registerPostTypeSchema, postType ] );

const { defaultFields } = useSelect(
( select ) => {
const { getEntityFields } = unlock( select( editorStore ) );
return {
defaultFields: getEntityFields( 'postType', postType ),
};
},
[ postType ]
);

const { records: authors, isResolving: isLoadingAuthors } =
useEntityRecords< Author >( 'root', 'user', { per_page: -1 } );

const fields = useMemo(
() =>
[
featuredImageField,
titleField,
{
...authorField,
elements: authors?.map( ( { id, name } ) => ( {
value: id,
label: name,
} ) ),
},
statusField,
dateField,
slugField,
parentField,
commentStatusField,
passwordField,
] as Field< BasePostWithEmbeddedAuthor >[],
[ authors ]
defaultFields.map(
( field: Field< BasePostWithEmbeddedAuthor > ) => {
if ( field.id === 'author' ) {
return {
...field,
elements: authors?.map( ( { id, name } ) => ( {
value: id,
label: name,
} ) ),
};
}

return field;
}
) as Field< BasePostWithEmbeddedAuthor >[],
[ authors, defaultFields ]
);

return {
Expand Down
41 changes: 41 additions & 0 deletions packages/editor/src/dataviews/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { store as editorStore } from '../store';

/**
* @typedef {import('@wordpress/dataviews').Action} Action
* @typedef {import('@wordpress/dataviews').Field} Field
*/

/**
Expand Down Expand Up @@ -53,3 +54,43 @@ export function unregisterEntityAction( kind, name, actionId ) {
_unregisterEntityAction( kind, name, actionId );
}
}

/**
* Registers a new DataViews field.
*
* This is an experimental API and is subject to change.
* it's only available in the Gutenberg plugin for now.
*
* @param {string} kind Entity kind.
* @param {string} name Entity name.
* @param {Field} config Field configuration.
*/
export function registerEntityField( kind, name, config ) {
const { registerEntityField: _registerEntityField } = unlock(
dispatch( editorStore )
);

if ( globalThis.IS_GUTENBERG_PLUGIN ) {
_registerEntityField( kind, name, config );
}
}

/**
* Unregisters a DataViews field.
*
* This is an experimental API and is subject to change.
* it's only available in the Gutenberg plugin for now.
*
* @param {string} kind Entity kind.
* @param {string} name Entity name.
* @param {string} fieldId Field ID.
*/
export function unregisterEntityField( kind, name, fieldId ) {
const { unregisterEntityField: _unregisterEntityField } = unlock(
dispatch( editorStore )
);

if ( globalThis.IS_GUTENBERG_PLUGIN ) {
_unregisterEntityField( kind, name, fieldId );
}
}
60 changes: 57 additions & 3 deletions packages/editor/src/dataviews/store/private-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* WordPress dependencies
*/
import { store as coreStore } from '@wordpress/core-data';
import type { Action } from '@wordpress/dataviews';
import type { Action, Field } from '@wordpress/dataviews';
import { doAction } from '@wordpress/hooks';

/**
Expand All @@ -24,6 +24,15 @@ import {
renamePost,
resetPost,
deletePost,
featuredImageField,
dateField,
parentField,
passwordField,
commentStatusField,
slugField,
statusField,
authorField,
titleField,
} from '@wordpress/fields';
import duplicateTemplatePart from '../actions/duplicate-template-part';

Expand Down Expand Up @@ -53,6 +62,32 @@ export function unregisterEntityAction(
};
}

export function registerEntityField< Item >(
kind: string,
name: string,
config: Field< Item >
) {
return {
type: 'REGISTER_ENTITY_FIELD' as const,
kind,
name,
config,
};
}

export function unregisterEntityField(
kind: string,
name: string,
fieldId: string
) {
return {
type: 'UNREGISTER_ENTITY_FIELD' as const,
kind,
name,
fieldId,
};
}

export function setIsReady( kind: string, name: string ) {
return {
type: 'SET_IS_READY' as const,
Expand All @@ -61,7 +96,7 @@ export function setIsReady( kind: string, name: string ) {
};
}

export const registerPostTypeActions =
export const registerPostTypeSchema =
( postType: string ) =>
async ( { registry }: { registry: any } ) => {
const isReady = unlock( registry.select( editorStore ) ).isEntityReady(
Expand Down Expand Up @@ -124,6 +159,18 @@ export const registerPostTypeActions =
permanentlyDeletePost,
];

const fields = [
featuredImageField,
titleField,
authorField,
statusField,
dateField,
slugField,
parentField,
commentStatusField,
passwordField,
];

registry.batch( () => {
actions.forEach( ( action ) => {
if ( ! action ) {
Expand All @@ -135,7 +182,14 @@ export const registerPostTypeActions =
action
);
} );
fields.forEach( ( field ) => {
unlock( registry.dispatch( editorStore ) ).registerEntityField(
'postType',
postType,
field
);
} );
} );

doAction( 'core.registerPostTypeActions', postType );
doAction( 'core.registerPostTypeSchema', postType );
};
4 changes: 4 additions & 0 deletions packages/editor/src/dataviews/store/private-selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export function getEntityActions( state: State, kind: string, name: string ) {
return state.actions[ kind ]?.[ name ] ?? EMPTY_ARRAY;
}

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

export function isEntityReady( state: State, kind: string, name: string ) {
return state.isReady[ kind ]?.[ name ];
}
39 changes: 38 additions & 1 deletion packages/editor/src/dataviews/store/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@
* WordPress dependencies
*/
import { combineReducers } from '@wordpress/data';
import type { Action } from '@wordpress/dataviews';
import type { Action, Field } from '@wordpress/dataviews';

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

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

Expand Down Expand Up @@ -64,7 +68,40 @@ function actions( state: ActionState = {}, action: ReduxAction ) {
return state;
}

function fields( state: FieldsState = {}, action: ReduxAction ) {
switch ( action.type ) {
case 'REGISTER_ENTITY_FIELD':
return {
...state,
[ action.kind ]: {
...state[ action.kind ],
[ action.name ]: [
...(
state[ action.kind ]?.[ action.name ] ?? []
).filter(
( _field ) => _field.id !== action.config.id
),
action.config,
],
},
};
case 'UNREGISTER_ENTITY_FIELD':
return {
...state,
[ action.kind ]: {
...state[ action.kind ],
[ action.name ]: (
state[ action.kind ]?.[ action.name ] ?? []
).filter( ( _field ) => _field.id !== action.fieldId ),
},
};
}

return state;
}

export default combineReducers( {
actions,
fields,
isReady,
} );
5 changes: 5 additions & 0 deletions packages/editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
} from './selectors';
import {
getEntityActions as _getEntityActions,
getEntityFields as _getEntityFields,
isEntityReady as _isEntityReady,
} from '../dataviews/store/private-selectors';

Expand Down Expand Up @@ -171,6 +172,10 @@ export function isEntityReady( state, ...args ) {
return _isEntityReady( state.dataviews, ...args );
}

export function getEntityFields( state, ...args ) {
return _getEntityFields( 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

0 comments on commit 96f6338

Please sign in to comment.