Skip to content

Commit

Permalink
Move entity-provider.js exports into hooks/index.ts so they are added…
Browse files Browse the repository at this point in the history
… to the docs (WordPress#63528)

* Reapply changes to fix bad rebase.

* Update import for entity-provider.js tests.

* Create a common EntityContext and import to appropriate places.

Co-authored-by: ryanwelcher <[email protected]>
Co-authored-by: youknowriad <[email protected]>
Co-authored-by: Mamaduka <[email protected]>
Co-authored-by: Taigistal <[email protected]>
  • Loading branch information
5 people authored Jul 17, 2024
1 parent 9ff25d7 commit 93d16e5
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 210 deletions.
41 changes: 41 additions & 0 deletions packages/core-data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,47 @@ The following set of react hooks available to import from the `@wordpress/core-d

<!-- START TOKEN(Autogenerated hooks|src/hooks/index.ts) -->

### useEntityBlockEditor

Hook that returns block content getters and setters for the nearest provided entity of the specified type.

The return value has the shape `[ blocks, onInput, onChange ]`. `onInput` is for block changes that don't create undo levels or dirty the post, non-persistent changes, and `onChange` is for persistent changes. They map directly to the props of a `BlockEditorProvider` and are intended to be used with it, or similar components or hooks.

_Parameters_

- _kind_ `string`: The entity kind.
- _name_ `string`: The entity name.
- _options_ `Object`:
- _options.id_ `[string]`: An entity ID to use instead of the context-provided one.

_Returns_

- `[unknown[], Function, Function]`: The block array and setters.

### useEntityId

Hook that returns the ID for the nearest provided entity of the specified type.

_Parameters_

- _kind_ `string`: The entity kind.
- _name_ `string`: The entity name.

### useEntityProp

Hook that returns the value and a setter for the specified property of the nearest provided entity of the specified type.

_Parameters_

- _kind_ `string`: The entity kind.
- _name_ `string`: The entity name.
- _prop_ `string`: The property name.
- _\_id_ `[string]`: An entity ID to use instead of the context-provided one.

_Returns_

- `[*, Function, *]`: An array where the first item is the property value, the second is the setter and the third is the full value object from REST API containing more information like `raw`, `rendered` and `protected` props.

### useEntityRecord

Resolves the specified entity record.
Expand Down
6 changes: 6 additions & 0 deletions packages/core-data/src/entity-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* WordPress dependencies
*/
import { createContext } from '@wordpress/element';

export const EntityContext = createContext( {} );
211 changes: 2 additions & 209 deletions packages/core-data/src/entity-provider.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
/**
* WordPress dependencies
*/
import {
createContext,
useContext,
useCallback,
useMemo,
} from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { parse, __unstableSerializeAndClean } from '@wordpress/blocks';
import { useContext, useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import { STORE_NAME } from './name';
import { updateFootnotesFromMeta } from './footnotes';

const EMPTY_ARRAY = [];

const EntityContext = createContext( {} );
import { EntityContext } from './entity-context';

/**
* Context provider component for providing
Expand Down Expand Up @@ -51,198 +39,3 @@ export default function EntityProvider( { kind, type: name, id, children } ) {
</EntityContext.Provider>
);
}

/**
* Hook that returns the ID for the nearest
* provided entity of the specified type.
*
* @param {string} kind The entity kind.
* @param {string} name The entity name.
*/
export function useEntityId( kind, name ) {
const context = useContext( EntityContext );
return context?.[ kind ]?.[ name ];
}

/**
* 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} name The entity name.
* @param {string} prop The property name.
* @param {string} [_id] An entity ID to use instead of the context-provided one.
*
* @return {[*, Function, *]} An array where the first item is the
* property value, the second is the
* setter and the third is the full value
* object from REST API containing more
* information like `raw`, `rendered` and
* `protected` props.
*/
export function useEntityProp( kind, name, prop, _id ) {
const providerId = useEntityId( kind, name );
const id = _id ?? providerId;

const { value, fullValue } = useSelect(
( select ) => {
const { getEntityRecord, getEditedEntityRecord } =
select( STORE_NAME );
const record = getEntityRecord( kind, name, id ); // Trigger resolver.
const editedRecord = getEditedEntityRecord( kind, name, id );
return record && editedRecord
? {
value: editedRecord[ prop ],
fullValue: record[ prop ],
}
: {};
},
[ kind, name, id, prop ]
);
const { editEntityRecord } = useDispatch( STORE_NAME );
const setValue = useCallback(
( newValue ) => {
editEntityRecord( kind, name, id, {
[ prop ]: newValue,
} );
},
[ editEntityRecord, kind, name, id, prop ]
);

return [ value, setValue, fullValue ];
}

const parsedBlocksCache = new WeakMap();

/**
* Hook that returns block content getters and setters for
* the nearest provided entity of the specified type.
*
* The return value has the shape `[ blocks, onInput, onChange ]`.
* `onInput` is for block changes that don't create undo levels
* or dirty the post, non-persistent changes, and `onChange` is for
* persistent changes. They map directly to the props of a
* `BlockEditorProvider` and are intended to be used with it,
* or similar components or hooks.
*
* @param {string} kind The entity kind.
* @param {string} name The entity name.
* @param {Object} options
* @param {string} [options.id] An entity ID to use instead of the context-provided one.
*
* @return {[unknown[], Function, Function]} The block array and setters.
*/
export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
const providerId = useEntityId( kind, name );
const id = _id ?? providerId;
const { getEntityRecord, getEntityRecordEdits } = useSelect( STORE_NAME );
const { content, editedBlocks, meta } = useSelect(
( select ) => {
if ( ! id ) {
return {};
}
const { getEditedEntityRecord } = select( STORE_NAME );
const editedRecord = getEditedEntityRecord( kind, name, id );
return {
editedBlocks: editedRecord.blocks,
content: editedRecord.content,
meta: editedRecord.meta,
};
},
[ kind, name, id ]
);
const { __unstableCreateUndoLevel, editEntityRecord } =
useDispatch( STORE_NAME );

const blocks = useMemo( () => {
if ( ! id ) {
return undefined;
}

if ( editedBlocks ) {
return editedBlocks;
}

if ( ! content || typeof content !== 'string' ) {
return EMPTY_ARRAY;
}

// If there's an edit, cache the parsed blocks by the edit.
// If not, cache by the original enity record.
const edits = getEntityRecordEdits( kind, name, id );
const isUnedited = ! edits || ! Object.keys( edits ).length;
const cackeKey = isUnedited ? getEntityRecord( kind, name, id ) : edits;
let _blocks = parsedBlocksCache.get( cackeKey );

if ( ! _blocks ) {
_blocks = parse( content );
parsedBlocksCache.set( cackeKey, _blocks );
}

return _blocks;
}, [
kind,
name,
id,
editedBlocks,
content,
getEntityRecord,
getEntityRecordEdits,
] );

const updateFootnotes = useCallback(
( _blocks ) => updateFootnotesFromMeta( _blocks, meta ),
[ meta ]
);

const onChange = useCallback(
( newBlocks, options ) => {
const noChange = blocks === newBlocks;
if ( noChange ) {
return __unstableCreateUndoLevel( kind, name, id );
}
const { selection, ...rest } = options;

// We create a new function here on every persistent edit
// to make sure the edit makes the post dirty and creates
// a new undo level.
const edits = {
selection,
content: ( { blocks: blocksForSerialization = [] } ) =>
__unstableSerializeAndClean( blocksForSerialization ),
...updateFootnotes( newBlocks ),
};

editEntityRecord( kind, name, id, edits, {
isCached: false,
...rest,
} );
},
[
kind,
name,
id,
blocks,
updateFootnotes,
__unstableCreateUndoLevel,
editEntityRecord,
]
);

const onInput = useCallback(
( newBlocks, options ) => {
const { selection, ...rest } = options;
const footnotesChanges = updateFootnotes( newBlocks );
const edits = { selection, ...footnotesChanges };

editEntityRecord( kind, name, id, edits, {
isCached: true,
...rest,
} );
},
[ kind, name, id, updateFootnotes, editEntityRecord ]
);

return [ blocks, onInput, onChange ];
}
3 changes: 3 additions & 0 deletions packages/core-data/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ export {
default as useResourcePermissions,
__experimentalUseResourcePermissions,
} from './use-resource-permissions';
export { default as useEntityBlockEditor } from './use-entity-block-editor';
export { default as useEntityId } from './use-entity-id';
export { default as useEntityProp } from './use-entity-prop';
Loading

0 comments on commit 93d16e5

Please sign in to comment.