From 6c0d2ae6fe5dbfb74962e181b9a045841f45d053 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 3 Jan 2024 10:25:21 +0100 Subject: [PATCH] Entities: Add config to read and write transient edits from edited post --- docs/reference-guides/data/data-core.md | 2 +- packages/core-data/README.md | 2 +- packages/core-data/src/entities.js | 16 ++++- packages/core-data/src/entity-provider.js | 11 ++-- packages/core-data/src/selectors.ts | 78 ++++++++++++++++++----- packages/editor/src/store/selectors.js | 16 +---- 6 files changed, 85 insertions(+), 40 deletions(-) diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index b80703dcc67b18..6850e447824040 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -249,7 +249,7 @@ _Parameters_ _Returns_ -- `any`: Entity config +- `EntityConfig | undefined`: Entity config ### getEntityRecord diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 6677a32df08dc9..ad336de40a33a1 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -570,7 +570,7 @@ _Parameters_ _Returns_ -- `any`: Entity config +- `EntityConfig | undefined`: Entity config ### getEntityRecord diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 444d66674d9839..60138ce0c6ad8f 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -9,6 +9,7 @@ import { capitalCase, pascalCase } from 'change-case'; import apiFetch from '@wordpress/api-fetch'; import { __ } from '@wordpress/i18n'; import { RichTextData } from '@wordpress/rich-text'; +import { __unstableSerializeAndClean, parse } from '@wordpress/blocks'; /** * Internal dependencies @@ -19,6 +20,13 @@ import { getSyncProvider } from './sync'; export const DEFAULT_ENTITY_KEY = 'id'; const POST_RAW_ATTRIBUTES = [ 'title', 'excerpt', 'content' ]; +const blocksTransientEntity = { + read: ( record ) => parse( record.content ), + write: ( record ) => ( { + ...record, + content: __unstableSerializeAndClean( record.content ), + } ), +}; export const rootEntitiesConfig = [ { @@ -138,7 +146,9 @@ export const rootEntitiesConfig = [ baseURL: '/wp/v2/sidebars', baseURLParams: { context: 'edit' }, plural: 'sidebars', - transientEdits: { blocks: true }, + transientEdits: { + blocks: blocksTransientEntity, + }, label: __( 'Widget areas' ), }, { @@ -147,7 +157,7 @@ export const rootEntitiesConfig = [ baseURL: '/wp/v2/widgets', baseURLParams: { context: 'edit' }, plural: 'widgets', - transientEdits: { blocks: true }, + transientEdits: { blocks: blocksTransientEntity }, label: __( 'Widgets' ), }, { @@ -320,7 +330,7 @@ async function loadPostTypeEntities() { name, label: postType.name, transientEdits: { - blocks: true, + blocks: blocksTransientEntity, selection: true, }, mergedEdits: { meta: true }, diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index 5dc19f5225c769..ccf2b321436272 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -8,7 +8,7 @@ import { useMemo, } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; -import { parse, __unstableSerializeAndClean } from '@wordpress/blocks'; +import { __unstableSerializeAndClean } from '@wordpress/blocks'; /** * Internal dependencies @@ -153,7 +153,7 @@ export function useEntityProp( kind, name, prop, _id ) { export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { const providerId = useEntityId( kind, name ); const id = _id ?? providerId; - const { content, editedBlocks, meta } = useSelect( + const { editedBlocks, meta } = useSelect( ( select ) => { if ( ! id ) { return {}; @@ -162,7 +162,6 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { const editedRecord = getEditedEntityRecord( kind, name, id ); return { editedBlocks: editedRecord.blocks, - content: editedRecord.content, meta: editedRecord.meta, }; }, @@ -180,10 +179,8 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { return editedBlocks; } - return content && typeof content !== 'function' - ? parse( content ) - : EMPTY_ARRAY; - }, [ id, editedBlocks, content ] ); + return EMPTY_ARRAY; + }, [ id, editedBlocks ] ); const updateFootnotes = useCallback( ( _blocks ) => updateFootnotesFromMeta( _blocks, meta ), diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 5751a80b9106cd..315e07b8a8bffa 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -83,10 +83,15 @@ interface EntityState< EntityRecord extends ET.EntityRecord > { revisions?: RevisionsQueriedData; } -interface EntityConfig { +type TransientPropertyConfig = boolean | { read: Function; write: Function }; +type EntityConfig = { name: string; kind: string; -} + key?: string; + __unstable_rest_base?: string; + getTitle?: ( entity: any ) => string; + transientEdits?: Record< string, TransientPropertyConfig >; +}; interface UserState { queries: Record< string, EntityRecordKey[] >; @@ -265,7 +270,7 @@ export function getEntityConfig( state: State, kind: string, name: string -): any { +): EntityConfig | undefined { return state.entities.config?.find( ( config ) => config.kind === kind && config.name === name ); @@ -670,7 +675,7 @@ export const __experimentalGetDirtyEntityRecords = createSelector( // when it's used as an object key. key: entityRecord ? entityRecord[ - entityConfig.key || DEFAULT_ENTITY_KEY + entityConfig?.key || DEFAULT_ENTITY_KEY ] : undefined, title: @@ -723,7 +728,7 @@ export const __experimentalGetEntitiesBeingSaved = createSelector( // when it's used as an object key. key: entityRecord ? entityRecord[ - entityConfig.key || DEFAULT_ENTITY_KEY + entityConfig?.key || DEFAULT_ENTITY_KEY ] : undefined, title: @@ -750,16 +755,56 @@ export const __experimentalGetEntitiesBeingSaved = createSelector( * * @return The entity record's edits. */ -export function getEntityRecordEdits( - state: State, - kind: string, - name: string, - recordId: EntityRecordKey -): Optional< any > { - return state.entities.records?.[ kind ]?.[ name ]?.edits?.[ - recordId as string | number - ]; -} +export const getEntityRecordEdits = createSelector( + ( + state: State, + kind: string, + name: string, + recordId: EntityRecordKey + ): Optional< any > => { + const { transientEdits } = getEntityConfig( state, kind, name ) ?? {}; + const regularEdits = + state.entities.records?.[ kind ]?.[ name ]?.edits?.[ + recordId as string | number + ]; + let result = regularEdits; + const rawRecord = getRawEntityRecord( state, kind, name, recordId ); + const recordWithEdits = rawRecord + ? { + ...rawRecord, + ...regularEdits, + } + : undefined; + Object.entries( transientEdits ?? {} ).forEach( + ( [ key, transientEditConfig ] ) => { + if ( + recordWithEdits && + regularEdits?.[ key ] === undefined && + typeof transientEditConfig === 'object' + ) { + result = { + ...result, + [ key ]: transientEditConfig.read( recordWithEdits ), + }; + } + } + ); + + return result; + }, + ( state: State, kind: string, name: string, recordId: EntityRecordKey ) => { + const context = 'default'; + return [ + state.entities.config, + state.entities.records?.[ kind ]?.[ name ]?.edits?.[ recordId ], + state.entities.records?.[ kind ]?.[ name ]?.queriedData?.items[ + context + ]?.[ recordId ], + state.entities.records?.[ kind ]?.[ name ]?.queriedData + ?.itemIsComplete[ context ]?.[ recordId ], + ]; + } +); /** * Returns the specified entity record's non transient edits. @@ -1152,6 +1197,9 @@ export function canUserEditEntityRecord( return false; } const resource = entityConfig.__unstable_rest_base; + if ( resource === undefined ) { + return false; + } return canUser( state, 'update', resource, recordId ); } diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 70d726638a0940..cd53b44b54072f 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -10,7 +10,6 @@ import { getFreeformContentHandlerName, getDefaultBlockName, __unstableSerializeAndClean, - parse, } from '@wordpress/blocks'; import { isInTheFuture, getDate } from '@wordpress/date'; import { addQueryArgs, cleanForSlug } from '@wordpress/url'; @@ -1112,18 +1111,9 @@ export const isPublishSidebarEnabled = createRegistrySelector( * @param {Object} state * @return {Array} Block list. */ -export const getEditorBlocks = createSelector( - ( state ) => { - return ( - getEditedPostAttribute( state, 'blocks' ) || - parse( getEditedPostContent( state ) ) - ); - }, - ( state ) => [ - getEditedPostAttribute( state, 'blocks' ), - getEditedPostContent( state ), - ] -); +export function getEditorBlocks( state ) { + return getEditedPostAttribute( state, 'blocks' ); +} /** * Returns true if the given panel was programmatically removed, or false otherwise.