From 971b98400b96b9df995891a1ebb23290b9af20d4 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Thu, 22 Aug 2019 14:05:42 -0700 Subject: [PATCH 1/6] Editor: Implement `EntityProvider` and use it to refactor custom sources with a backwards compatibility hook for meta sources. --- package-lock.json | 2 + packages/block-editor/package.json | 1 + .../src/components/block-list/block.js | 61 +++++- packages/core-data/package.json | 1 + packages/core-data/src/entity-provider.js | 76 +++++++ packages/core-data/src/index.js | 2 + .../editor/src/components/provider/index.js | 28 ++- packages/editor/src/store/actions.js | 206 +----------------- .../editor/src/store/block-sources/README.md | 22 -- .../store/block-sources/__mocks__/index.js | 1 - .../editor/src/store/block-sources/index.js | 6 - .../editor/src/store/block-sources/meta.js | 55 ----- packages/editor/src/store/test/actions.js | 1 - 13 files changed, 159 insertions(+), 303 deletions(-) create mode 100644 packages/core-data/src/entity-provider.js delete mode 100644 packages/editor/src/store/block-sources/README.md delete mode 100644 packages/editor/src/store/block-sources/__mocks__/index.js delete mode 100644 packages/editor/src/store/block-sources/index.js delete mode 100644 packages/editor/src/store/block-sources/meta.js diff --git a/package-lock.json b/package-lock.json index 3bc1f7d1b27788..ab48f3cac906c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4735,6 +4735,7 @@ "@wordpress/blocks": "file:packages/blocks", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", + "@wordpress/core-data": "file:packages/core-data", "@wordpress/data": "file:packages/data", "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/dom": "file:packages/dom", @@ -4886,6 +4887,7 @@ "@wordpress/data": "file:packages/data", "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", + "@wordpress/element": "file:packages/element", "@wordpress/url": "file:packages/url", "equivalent-key-map": "^0.2.2", "lodash": "^4.17.14", diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index ac30512879d26d..e6d9f9e978ab58 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -28,6 +28,7 @@ "@wordpress/blocks": "file:../blocks", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", "@wordpress/data": "file:../data", "@wordpress/deprecated": "file:../deprecated", "@wordpress/dom": "file:../dom", diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 076fe6d9bc9904..c967fda8a29b55 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -8,7 +8,15 @@ import { animated } from 'react-spring/web.cjs'; /** * WordPress dependencies */ -import { useRef, useEffect, useLayoutEffect, useState } from '@wordpress/element'; +import { useEntityProp } from '@wordpress/core-data'; +import { + useMemo, + useCallback, + useRef, + useEffect, + useLayoutEffect, + useState, +} from '@wordpress/element'; import { focus, isTextField, @@ -52,6 +60,51 @@ import Inserter from '../inserter'; import { isInsideRootBlock } from '../../utils/dom'; import useMovingAnimation from './moving-animation'; +const EMPTY_OBJECT = {}; +function useMetaAttributeSourceBackwardsCompatibility( + name, + _attributes, + _setAttributes +) { + const { attributes: attributeTypes = EMPTY_OBJECT } = + getBlockType( name ) || EMPTY_OBJECT; + let [ attributes, setAttributes ] = [ _attributes, _setAttributes ]; + + if ( Object.values( attributeTypes ).some( ( type ) => type.source === 'meta' ) ) { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [ meta, setMeta ] = useEntityProp( 'postType', 'post', 'meta' ); + + // eslint-disable-next-line react-hooks/rules-of-hooks + attributes = useMemo( + () => ( { + ..._attributes, + ...Object.keys( attributeTypes ).reduce( ( acc, key ) => { + if ( attributeTypes[ key ].source === 'meta' ) { + acc[ key ] = meta[ attributeTypes[ key ].meta ]; + } + return acc; + }, {} ), + } ), + [ attributeTypes, meta, _attributes ] + ); + + // eslint-disable-next-line react-hooks/rules-of-hooks + setAttributes = useCallback( + ( ...args ) => { + Object.keys( args[ 0 ] ).forEach( ( key ) => { + if ( attributeTypes[ key ].source === 'meta' ) { + setMeta( { [ attributeTypes[ key ].meta ]: args[ 0 ][ key ] } ); + } + } ); + return _setAttributes( ...args ); + }, + [ attributeTypes, setMeta, _setAttributes ] + ); + } + + return [ attributes, setAttributes ]; +} + /** * Prevents default dragging behavior within a block to allow for multi- * selection to take effect unhampered. @@ -102,6 +155,12 @@ function BlockListBlock( { isNavigationMode, enableNavigationMode, } ) { + [ attributes, setAttributes ] = useMetaAttributeSourceBackwardsCompatibility( + name, + attributes, + setAttributes + ); + // Random state used to rerender the component if needed, ideally we don't need this const [ , updateRerenderState ] = useState( {} ); const rerender = () => updateRerenderState( {} ); diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 3da791a43f81c7..d90bedb350e3ef 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -27,6 +27,7 @@ "@wordpress/data": "file:../data", "@wordpress/deprecated": "file:../deprecated", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/element": "file:../element", "@wordpress/url": "file:../url", "equivalent-key-map": "^0.2.2", "lodash": "^4.17.14", diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js new file mode 100644 index 00000000000000..55a4919c7bddf0 --- /dev/null +++ b/packages/core-data/src/entity-provider.js @@ -0,0 +1,76 @@ +/** + * WordPress dependencies + */ +import { createContext, useContext, useCallback } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { defaultEntities } from './entities'; + +const entities = defaultEntities.reduce( + ( acc, entity ) => { + if ( ! acc[ entity.kind ] ) { + acc[ entity.kind ] = {}; + } + acc[ entity.kind ][ entity.name ] = { context: createContext() }; + return acc; + }, + { postType: { post: { context: createContext() } } } +); + +/** + * Context provider component for providing + * an entity for a specific entity type. + * + * @param {Object} props The component's props. + * @param {string} props.kind The entity kind. + * @param {string} props.type The entity type. + * @param {number} props.id The entity ID. + * @param {*} props.children The children to wrap. + * + * @return {Object} The provided children, wrapped with + * the entity's context provider. + */ +export default function EntityProvider( { kind, type, id, children } ) { + const Provider = entities[ kind ][ type ].context.Provider; + return { children }; +} + +/** + * Hook that returns the value and a setter for the + * specified property of the nearest provided + * entity of the specified type. + * + * @param {string} kind The entity kind. + * @param {string} type The entity type. + * @param {string} prop The property name. + * + * @return {[*, Function]} A tuple where the first item is the + * property value and the second is the + * setter. + */ +export function useEntityProp( kind, type, prop ) { + const id = useContext( entities[ kind ][ type ].context ); + + const value = useSelect( + ( select ) => { + const entity = select( 'core' ).getEditedEntityRecord( kind, type, id ); + return entity && entity[ prop ]; + }, + [ kind, type, id, prop ] + ); + + const { editEntityRecord } = useDispatch( 'core' ); + const setValue = useCallback( + ( newValue ) => { + editEntityRecord( kind, type, id, { + [ prop ]: newValue, + } ); + }, + [ kind, type, id, prop ] + ); + + return [ value, setValue ]; +} diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index b0c5718ab9b888..2cdddb960e448b 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -48,3 +48,5 @@ registerStore( REDUCER_KEY, { selectors: { ...selectors, ...entitySelectors }, resolvers: { ...resolvers, ...entityResolvers }, } ); + +export { default as EntityProvider, useEntityProp } from './entity-provider'; diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 6cd07c572211af..c386f02097167b 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -11,6 +11,7 @@ import { compose } from '@wordpress/compose'; import { Component } from '@wordpress/element'; import { withDispatch, withSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; +import { EntityProvider } from '@wordpress/core-data'; import { BlockEditorProvider, transformStyles } from '@wordpress/block-editor'; import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; @@ -164,6 +165,7 @@ class EditorProvider extends Component { const { canUserUseUnfilteredHTML, children, + post, blocks, resetEditorBlocks, isReady, @@ -185,18 +187,20 @@ class EditorProvider extends Component { ); return ( - - { children } - - - { editorSettings.__experimentalBlockDirectory && } - + + + { children } + + + { editorSettings.__experimentalBlockDirectory && } + + ); } } diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index bd3076ef7c3b57..a95fcce44db1ed 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -9,7 +9,6 @@ import { has, castArray } from 'lodash'; import deprecated from '@wordpress/deprecated'; import { dispatch, select, apiFetch } from '@wordpress/data-controls'; import { parse, synchronizeBlocksWithTemplate } from '@wordpress/blocks'; -import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -25,120 +24,6 @@ import { getNotificationArgumentsForTrashFail, } from './utils/notice-builder'; import serializeBlocks from './utils/serialize-blocks'; -import { awaitNextStateChange, getRegistry } from './controls'; -import * as sources from './block-sources'; - -/** - * Map of Registry instance to WeakMap of dependencies by custom source. - * - * @type WeakMap> - */ -const lastBlockSourceDependenciesByRegistry = new WeakMap; - -/** - * Given a blocks array, returns a blocks array with sourced attribute values - * applied. The reference will remain consistent with the original argument if - * no attribute values must be overridden. If sourced values are applied, the - * return value will be a modified copy of the original array. - * - * @param {WPBlock[]} blocks Original blocks array. - * - * @return {WPBlock[]} Blocks array with sourced values applied. - */ -function* getBlocksWithSourcedAttributes( blocks ) { - const registry = yield getRegistry(); - if ( ! lastBlockSourceDependenciesByRegistry.has( registry ) ) { - return blocks; - } - - const blockSourceDependencies = lastBlockSourceDependenciesByRegistry.get( registry ); - - let workingBlocks = blocks; - for ( let i = 0; i < blocks.length; i++ ) { - let block = blocks[ i ]; - const blockType = yield select( 'core/blocks', 'getBlockType', block.name ); - - for ( const [ attributeName, schema ] of Object.entries( blockType.attributes ) ) { - if ( ! sources[ schema.source ] || ! sources[ schema.source ].apply ) { - continue; - } - - if ( ! blockSourceDependencies.has( sources[ schema.source ] ) ) { - continue; - } - - const dependencies = blockSourceDependencies.get( sources[ schema.source ] ); - const sourcedAttributeValue = sources[ schema.source ].apply( schema, dependencies ); - - // It's only necessary to apply the value if it differs from the - // block's locally-assigned value, to avoid needlessly resetting - // the block editor. - if ( sourcedAttributeValue === block.attributes[ attributeName ] ) { - continue; - } - - // Create a shallow clone to mutate, leaving the original intact. - if ( workingBlocks === blocks ) { - workingBlocks = [ ...workingBlocks ]; - } - - block = { - ...block, - attributes: { - ...block.attributes, - [ attributeName ]: sourcedAttributeValue, - }, - }; - - workingBlocks.splice( i, 1, block ); - } - - // Recurse to apply source attributes to inner blocks. - if ( block.innerBlocks.length ) { - const appliedInnerBlocks = yield* getBlocksWithSourcedAttributes( block.innerBlocks ); - if ( appliedInnerBlocks !== block.innerBlocks ) { - if ( workingBlocks === blocks ) { - workingBlocks = [ ...workingBlocks ]; - } - - block = { - ...block, - innerBlocks: appliedInnerBlocks, - }; - - workingBlocks.splice( i, 1, block ); - } - } - } - - return workingBlocks; -} - -/** - * Refreshes the last block source dependencies, optionally for a given subset - * of sources (defaults to the full set of sources). - * - * @param {?Array} sourcesToUpdate Optional subset of sources to reset. - * - * @yield {Object} Yielded actions or control descriptors. - */ -function* resetLastBlockSourceDependencies( sourcesToUpdate = Object.values( sources ) ) { - if ( ! sourcesToUpdate.length ) { - return; - } - - const registry = yield getRegistry(); - if ( ! lastBlockSourceDependenciesByRegistry.has( registry ) ) { - lastBlockSourceDependenciesByRegistry.set( registry, new WeakMap ); - } - - const lastBlockSourceDependencies = lastBlockSourceDependenciesByRegistry.get( registry ); - - for ( const source of sourcesToUpdate ) { - const dependencies = yield* source.getDependencies(); - lastBlockSourceDependencies.set( source, dependencies ); - } -} /** * Returns an action generator used in signalling that editor has initialized with @@ -168,7 +53,6 @@ export function* setupEditor( post, edits, template ) { } yield resetPost( post ); - yield* resetLastBlockSourceDependencies(); yield { type: 'SETUP_EDITOR', post, @@ -186,7 +70,6 @@ export function* setupEditor( post, edits, template ) { ) { yield editPost( edits ); } - yield* __experimentalSubscribeSources(); } /** @@ -199,55 +82,6 @@ export function __experimentalTearDownEditor() { return { type: 'TEAR_DOWN_EDITOR' }; } -/** - * Returns an action generator which loops to await the next state change, - * calling to reset blocks when a block source dependencies change. - * - * @yield {Object} Action object. - */ -export function* __experimentalSubscribeSources() { - while ( true ) { - yield awaitNextStateChange(); - - // The bailout case: If the editor becomes unmounted, it will flag - // itself as non-ready. Effectively unsubscribes from the registry. - const isStillReady = yield select( STORE_KEY, '__unstableIsEditorReady' ); - if ( ! isStillReady ) { - break; - } - - const registry = yield getRegistry(); - - let reset = false; - for ( const source of Object.values( sources ) ) { - if ( ! source.getDependencies ) { - continue; - } - - const dependencies = yield* source.getDependencies(); - - if ( ! lastBlockSourceDependenciesByRegistry.has( registry ) ) { - lastBlockSourceDependenciesByRegistry.set( registry, new WeakMap ); - } - - const lastBlockSourceDependencies = lastBlockSourceDependenciesByRegistry.get( registry ); - const lastDependencies = lastBlockSourceDependencies.get( source ); - - if ( ! isShallowEqual( dependencies, lastDependencies ) ) { - lastBlockSourceDependencies.set( source, dependencies ); - - // Allow the loop to continue in order to assign latest - // dependencies values, but mark for reset. - reset = true; - } - } - - if ( reset ) { - yield resetEditorBlocks( yield select( STORE_KEY, 'getEditorBlocks' ), { __unstableShouldCreateUndoLevel: false } ); - } - } -} - /** * Returns an action object used in signalling that the latest version of the * post has been received, either by initialization or save. @@ -793,44 +627,7 @@ export function unlockPostSaving( lockName ) { * @yield {Object} Action object */ export function* resetEditorBlocks( blocks, options = {} ) { - const lastBlockAttributesChange = yield select( 'core/block-editor', '__experimentalGetLastBlockAttributeChanges' ); - - // Sync to sources from block attributes updates. - if ( lastBlockAttributesChange ) { - const updatedSources = new Set; - const updatedBlockTypes = new Set; - for ( const [ clientId, attributes ] of Object.entries( lastBlockAttributesChange ) ) { - const blockName = yield select( 'core/block-editor', 'getBlockName', clientId ); - if ( updatedBlockTypes.has( blockName ) ) { - continue; - } - - updatedBlockTypes.add( blockName ); - const blockType = yield select( 'core/blocks', 'getBlockType', blockName ); - - for ( const [ attributeName, newAttributeValue ] of Object.entries( attributes ) ) { - if ( ! blockType.attributes.hasOwnProperty( attributeName ) ) { - continue; - } - - const schema = blockType.attributes[ attributeName ]; - const source = sources[ schema.source ]; - - if ( source && source.update ) { - yield* source.update( schema, newAttributeValue ); - updatedSources.add( source ); - } - } - } - - // Dependencies are reset so that source dependencies subscription - // skips a reset which would otherwise occur by dependencies change. - // This assures that at most one reset occurs per block change. - yield* resetLastBlockSourceDependencies( Array.from( updatedSources ) ); - } - - const edits = { blocks: yield* getBlocksWithSourcedAttributes( blocks ) }; - + const edits = { blocks }; if ( options.__unstableShouldCreateUndoLevel !== false ) { // We create a new function here on every persistent edit // to make sure the edit makes the post dirty and creates @@ -838,7 +635,6 @@ export function* resetEditorBlocks( blocks, options = {} ) { edits.content = ( { blocks: blocksForSerialization = [] } ) => serializeBlocks( blocksForSerialization ); } - yield* editPost( edits ); } diff --git a/packages/editor/src/store/block-sources/README.md b/packages/editor/src/store/block-sources/README.md deleted file mode 100644 index 0c16d12b3159d2..00000000000000 --- a/packages/editor/src/store/block-sources/README.md +++ /dev/null @@ -1,22 +0,0 @@ -Block Sources -============= - -By default, the blocks module supports only attributes serialized into a block's comment demarcations, or those sourced from a [standard set of sources](https://developer.wordpress.org/block-editor/developers/block-api/block-attributes/). Since the blocks module is intended to be used in a number of contexts outside the post editor, the implementation of additional context-specific sources must be implemented as an external process. - -The post editor supports such additional sources for attributes (e.g. `meta` source). - -These sources are implemented here using a uniform interface for applying and responding to block updates to sourced attributes. In the future, this interface may be generalized to allow third-party extensions to either extend the post editor sources or implement their own in custom renderings of a block editor. - -## Source API - -### `getDependencies` - -Store control called on every store change, expected to return an object whose values represent the data blocks assigned this source depend on. When these values change, all blocks assigned this source are automatically updated. The value returned from this function is passed as the second argument of the source's `apply` function, where it is expected to be used as shared data relevant for sourcing the attribute value. - -### `apply` - -Function called to retrieve an attribute value for a block. Given the attribute schema and the dependencies defined by the source's `getDependencies`, the function should return the expected attribute value. - -### `update` - -Store control called when a single block's attributes have been updated, before the new block value has taken effect (i.e. before `apply` and `applyAll` are once again called). Given the attribute schema and updated value, the control should reflect the update on the source. diff --git a/packages/editor/src/store/block-sources/__mocks__/index.js b/packages/editor/src/store/block-sources/__mocks__/index.js deleted file mode 100644 index cb0ff5c3b541f6..00000000000000 --- a/packages/editor/src/store/block-sources/__mocks__/index.js +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/packages/editor/src/store/block-sources/index.js b/packages/editor/src/store/block-sources/index.js deleted file mode 100644 index 542d774c313ce9..00000000000000 --- a/packages/editor/src/store/block-sources/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Internal dependencies - */ -import * as meta from './meta'; - -export { meta }; diff --git a/packages/editor/src/store/block-sources/meta.js b/packages/editor/src/store/block-sources/meta.js deleted file mode 100644 index 3910395c4a740d..00000000000000 --- a/packages/editor/src/store/block-sources/meta.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * WordPress dependencies - */ -import { select } from '@wordpress/data-controls'; - -/** - * Internal dependencies - */ -import { editPost } from '../actions'; - -/** - * Store control invoked upon a state change, responsible for returning an - * object of dependencies. When a change in dependencies occurs (by shallow - * equality of the returned object), blocks are reset to apply the new sourced - * value. - * - * @yield {Object} Optional yielded controls. - * - * @return {Object} Dependencies as object. - */ -export function* getDependencies() { - return { - meta: yield select( 'core/editor', 'getEditedPostAttribute', 'meta' ), - }; -} - -/** - * Given an attribute schema and dependencies data, returns a source value. - * - * @param {Object} schema Block type attribute schema. - * @param {Object} dependencies Source dependencies. - * @param {Object} dependencies.meta Post meta. - * - * @return {Object} Block attribute value. - */ -export function apply( schema, { meta } ) { - return meta[ schema.meta ]; -} - -/** - * Store control invoked upon a block attributes update, responsible for - * reflecting an update in a meta value. - * - * @param {Object} schema Block type attribute schema. - * @param {*} value Updated block attribute value. - * - * @yield {Object} Yielded action objects or store controls. - */ -export function* update( schema, value ) { - yield editPost( { - meta: { - [ schema.meta ]: value, - }, - } ); -} diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index 3c34195bb35415..255c0ea84af397 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -14,7 +14,6 @@ import { } from '../constants'; jest.mock( '@wordpress/data-controls' ); -jest.mock( '../block-sources' ); select.mockImplementation( ( ...args ) => { const { select: actualSelect } = jest From 9b19acb764584740ac71f4b92825550eab54ee9f Mon Sep 17 00:00:00 2001 From: epiqueras Date: Thu, 22 Aug 2019 15:19:57 -0700 Subject: [PATCH 2/6] Core Data: Handle dynamic entity contexts using a proxy. --- packages/core-data/src/entity-provider.js | 27 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index 55a4919c7bddf0..0fe0ed10514b10 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -7,18 +7,33 @@ import { useSelect, useDispatch } from '@wordpress/data'; /** * Internal dependencies */ -import { defaultEntities } from './entities'; +import { defaultEntities, kinds } from './entities'; -const entities = defaultEntities.reduce( - ( acc, entity ) => { +const entities = { + ...defaultEntities.reduce( ( acc, entity ) => { if ( ! acc[ entity.kind ] ) { acc[ entity.kind ] = {}; } acc[ entity.kind ][ entity.name ] = { context: createContext() }; return acc; - }, - { postType: { post: { context: createContext() } } } -); + }, {} ), + ...kinds.reduce( ( acc, kind ) => { + acc[ kind.name ] = new Proxy( + {}, + { + get( target, name ) { + if ( Reflect.has( target, name ) ) { + return Reflect.get( ...arguments ); + } + const value = { context: createContext() }; + Reflect.set( target, name, value ); + return value; + }, + } + ); + return acc; + }, {} ), +}; /** * Context provider component for providing From c48487453978609a3b488c65b4b346a9ad260183 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Fri, 23 Aug 2019 10:32:47 -0700 Subject: [PATCH 3/6] Block Editor: Replace dependency on Core Data with an Editor filter. --- package-lock.json | 1 - packages/block-editor/package.json | 1 - .../src/components/block-list/block.js | 61 +--------------- .../custom-sources-backwards-compatibility.js | 73 +++++++++++++++++++ packages/editor/src/hooks/index.js | 1 + 5 files changed, 75 insertions(+), 62 deletions(-) create mode 100644 packages/editor/src/hooks/custom-sources-backwards-compatibility.js diff --git a/package-lock.json b/package-lock.json index ab48f3cac906c3..63d5e0ce68e6e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4735,7 +4735,6 @@ "@wordpress/blocks": "file:packages/blocks", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", - "@wordpress/core-data": "file:packages/core-data", "@wordpress/data": "file:packages/data", "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/dom": "file:packages/dom", diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index e6d9f9e978ab58..ac30512879d26d 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -28,7 +28,6 @@ "@wordpress/blocks": "file:../blocks", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", - "@wordpress/core-data": "file:../core-data", "@wordpress/data": "file:../data", "@wordpress/deprecated": "file:../deprecated", "@wordpress/dom": "file:../dom", diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index c967fda8a29b55..076fe6d9bc9904 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -8,15 +8,7 @@ import { animated } from 'react-spring/web.cjs'; /** * WordPress dependencies */ -import { useEntityProp } from '@wordpress/core-data'; -import { - useMemo, - useCallback, - useRef, - useEffect, - useLayoutEffect, - useState, -} from '@wordpress/element'; +import { useRef, useEffect, useLayoutEffect, useState } from '@wordpress/element'; import { focus, isTextField, @@ -60,51 +52,6 @@ import Inserter from '../inserter'; import { isInsideRootBlock } from '../../utils/dom'; import useMovingAnimation from './moving-animation'; -const EMPTY_OBJECT = {}; -function useMetaAttributeSourceBackwardsCompatibility( - name, - _attributes, - _setAttributes -) { - const { attributes: attributeTypes = EMPTY_OBJECT } = - getBlockType( name ) || EMPTY_OBJECT; - let [ attributes, setAttributes ] = [ _attributes, _setAttributes ]; - - if ( Object.values( attributeTypes ).some( ( type ) => type.source === 'meta' ) ) { - // eslint-disable-next-line react-hooks/rules-of-hooks - const [ meta, setMeta ] = useEntityProp( 'postType', 'post', 'meta' ); - - // eslint-disable-next-line react-hooks/rules-of-hooks - attributes = useMemo( - () => ( { - ..._attributes, - ...Object.keys( attributeTypes ).reduce( ( acc, key ) => { - if ( attributeTypes[ key ].source === 'meta' ) { - acc[ key ] = meta[ attributeTypes[ key ].meta ]; - } - return acc; - }, {} ), - } ), - [ attributeTypes, meta, _attributes ] - ); - - // eslint-disable-next-line react-hooks/rules-of-hooks - setAttributes = useCallback( - ( ...args ) => { - Object.keys( args[ 0 ] ).forEach( ( key ) => { - if ( attributeTypes[ key ].source === 'meta' ) { - setMeta( { [ attributeTypes[ key ].meta ]: args[ 0 ][ key ] } ); - } - } ); - return _setAttributes( ...args ); - }, - [ attributeTypes, setMeta, _setAttributes ] - ); - } - - return [ attributes, setAttributes ]; -} - /** * Prevents default dragging behavior within a block to allow for multi- * selection to take effect unhampered. @@ -155,12 +102,6 @@ function BlockListBlock( { isNavigationMode, enableNavigationMode, } ) { - [ attributes, setAttributes ] = useMetaAttributeSourceBackwardsCompatibility( - name, - attributes, - setAttributes - ); - // Random state used to rerender the component if needed, ideally we don't need this const [ , updateRerenderState ] = useState( {} ); const rerender = () => updateRerenderState( {} ); diff --git a/packages/editor/src/hooks/custom-sources-backwards-compatibility.js b/packages/editor/src/hooks/custom-sources-backwards-compatibility.js new file mode 100644 index 00000000000000..0e6173b99382d6 --- /dev/null +++ b/packages/editor/src/hooks/custom-sources-backwards-compatibility.js @@ -0,0 +1,73 @@ +/** + * WordPress dependencies + */ +import { getBlockType } from '@wordpress/blocks'; +import { useEntityProp } from '@wordpress/core-data'; +import { useMemo, useCallback } from '@wordpress/element'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { addFilter } from '@wordpress/hooks'; + +const EMPTY_OBJECT = {}; +function useMetaAttributeSource( name, _attributes, _setAttributes ) { + const { attributes: attributeTypes = EMPTY_OBJECT } = + getBlockType( name ) || EMPTY_OBJECT; + let [ attributes, setAttributes ] = [ _attributes, _setAttributes ]; + + if ( Object.values( attributeTypes ).some( ( type ) => type.source === 'meta' ) ) { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [ meta, setMeta ] = useEntityProp( 'postType', 'post', 'meta' ); + + // eslint-disable-next-line react-hooks/rules-of-hooks + attributes = useMemo( + () => ( { + ..._attributes, + ...Object.keys( attributeTypes ).reduce( ( acc, key ) => { + if ( attributeTypes[ key ].source === 'meta' ) { + acc[ key ] = meta[ attributeTypes[ key ].meta ]; + } + return acc; + }, {} ), + } ), + [ attributeTypes, meta, _attributes ] + ); + + // eslint-disable-next-line react-hooks/rules-of-hooks + setAttributes = useCallback( + ( ...args ) => { + Object.keys( args[ 0 ] ).forEach( ( key ) => { + if ( attributeTypes[ key ].source === 'meta' ) { + setMeta( { [ attributeTypes[ key ].meta ]: args[ 0 ][ key ] } ); + } + } ); + return _setAttributes( ...args ); + }, + [ attributeTypes, setMeta, _setAttributes ] + ); + } + + return [ attributes, setAttributes ]; +} +const withMetaAttributeSource = createHigherOrderComponent( + ( BlockListBlock ) => ( { attributes, setAttributes, name, ...props } ) => { + [ attributes, setAttributes ] = useMetaAttributeSource( + name, + attributes, + setAttributes + ); + return ( + + ); + }, + 'withMetaAttributeSource' +); + +addFilter( + 'editor.BlockListBlock', + 'core/editor/custom-sources-backwards-compatibility/with-meta-attribute-source', + withMetaAttributeSource +); diff --git a/packages/editor/src/hooks/index.js b/packages/editor/src/hooks/index.js index 2c8a61d9802521..6e0934d63c0cfa 100644 --- a/packages/editor/src/hooks/index.js +++ b/packages/editor/src/hooks/index.js @@ -1,4 +1,5 @@ /** * Internal dependencies */ +import './custom-sources-backwards-compatibility'; import './default-autocompleters'; From 7e1b75018897de9e781d235d48395c53565e23c6 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Mon, 16 Sep 2019 15:04:09 -0400 Subject: [PATCH 4/6] Core Data: Fix linting error from rebase. --- package-lock.json | 2 +- packages/core-data/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63d5e0ce68e6e4..6195daff5a18df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4885,8 +4885,8 @@ "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/data": "file:packages/data", "@wordpress/deprecated": "file:packages/deprecated", - "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/element": "file:packages/element", + "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/url": "file:packages/url", "equivalent-key-map": "^0.2.2", "lodash": "^4.17.14", diff --git a/packages/core-data/package.json b/packages/core-data/package.json index d90bedb350e3ef..7b46a527f675ca 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -26,8 +26,8 @@ "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/data": "file:../data", "@wordpress/deprecated": "file:../deprecated", - "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/element": "file:../element", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/url": "file:../url", "equivalent-key-map": "^0.2.2", "lodash": "^4.17.14", From c04c3f8cbde54480d941ba1f1e198a97aec935f9 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Fri, 20 Sep 2019 09:05:24 -0700 Subject: [PATCH 5/6] Core Data: Simplify proxy usage into a context specific proxy. --- packages/core-data/src/entity-provider.js | 31 +++++++++++------------ 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index 0fe0ed10514b10..419485f565498f 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -9,7 +9,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; */ import { defaultEntities, kinds } from './entities'; -const entities = { +const _entities = { ...defaultEntities.reduce( ( acc, entity ) => { if ( ! acc[ entity.kind ] ) { acc[ entity.kind ] = {}; @@ -18,22 +18,21 @@ const entities = { return acc; }, {} ), ...kinds.reduce( ( acc, kind ) => { - acc[ kind.name ] = new Proxy( - {}, - { - get( target, name ) { - if ( Reflect.has( target, name ) ) { - return Reflect.get( ...arguments ); - } - const value = { context: createContext() }; - Reflect.set( target, name, value ); - return value; - }, - } - ); + acc[ kind.name ] = {}; return acc; }, {} ), }; +const getEntity = ( kind, type ) => { + if ( ! _entities[ kind ] ) { + throw new Error( `Missing entity config for kind: ${ kind }.` ); + } + + if ( ! _entities[ kind ][ type ] ) { + _entities[ kind ][ type ] = { context: createContext() }; + } + + return _entities[ kind ][ type ]; +}; /** * Context provider component for providing @@ -49,7 +48,7 @@ const entities = { * the entity's context provider. */ export default function EntityProvider( { kind, type, id, children } ) { - const Provider = entities[ kind ][ type ].context.Provider; + const Provider = getEntity( kind, type ).context.Provider; return { children }; } @@ -67,7 +66,7 @@ export default function EntityProvider( { kind, type, id, children } ) { * setter. */ export function useEntityProp( kind, type, prop ) { - const id = useContext( entities[ kind ][ type ].context ); + const id = useContext( getEntity( kind, type ).context ); const value = useSelect( ( select ) => { From 3a46a478fcee808d72c980b2dee335974d5119f7 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Mon, 23 Sep 2019 06:56:34 -0700 Subject: [PATCH 6/6] Core Data: Clean up variable names. --- packages/core-data/src/entity-provider.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index 419485f565498f..7870e0217cc974 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -9,7 +9,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; */ import { defaultEntities, kinds } from './entities'; -const _entities = { +const entities = { ...defaultEntities.reduce( ( acc, entity ) => { if ( ! acc[ entity.kind ] ) { acc[ entity.kind ] = {}; @@ -23,15 +23,15 @@ const _entities = { }, {} ), }; const getEntity = ( kind, type ) => { - if ( ! _entities[ kind ] ) { + if ( ! entities[ kind ] ) { throw new Error( `Missing entity config for kind: ${ kind }.` ); } - if ( ! _entities[ kind ][ type ] ) { - _entities[ kind ][ type ] = { context: createContext() }; + if ( ! entities[ kind ][ type ] ) { + entities[ kind ][ type ] = { context: createContext() }; } - return _entities[ kind ][ type ]; + return entities[ kind ][ type ]; }; /**