UNPKG

@wordpress/core-data

Version:
258 lines (246 loc) 7.85 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = EntityProvider; exports.useEntityBlockEditor = useEntityBlockEditor; exports.useEntityId = useEntityId; exports.useEntityProp = useEntityProp; var _react = require("react"); var _element = require("@wordpress/element"); var _data = require("@wordpress/data"); var _blocks2 = require("@wordpress/blocks"); var _name = require("./name"); var _footnotes = require("./footnotes"); var _entities = require("./entities"); /** * WordPress dependencies */ /** * Internal dependencies */ /** @typedef {import('@wordpress/blocks').WPBlock} WPBlock */ const EMPTY_ARRAY = []; /** * Internal dependencies */ const entityContexts = { ..._entities.rootEntitiesConfig.reduce((acc, loader) => { if (!acc[loader.kind]) { acc[loader.kind] = {}; } acc[loader.kind][loader.name] = { context: (0, _element.createContext)(undefined) }; return acc; }, {}), ..._entities.additionalEntityConfigLoaders.reduce((acc, loader) => { acc[loader.kind] = {}; return acc; }, {}) }; const getEntityContext = (kind, name) => { if (!entityContexts[kind]) { throw new Error(`Missing entity config for kind: ${kind}.`); } if (!entityContexts[kind][name]) { entityContexts[kind][name] = { context: (0, _element.createContext)(undefined) }; } return entityContexts[kind][name].context; }; /** * Context provider component for providing * an entity for a specific entity. * * @param {Object} props The component's props. * @param {string} props.kind The entity kind. * @param {string} props.type The entity name. * @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. */ function EntityProvider({ kind, type: name, id, children }) { const Provider = getEntityContext(kind, name).Provider; return (0, _react.createElement)(Provider, { value: id }, children); } /** * 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. */ function useEntityId(kind, name) { return (0, _element.useContext)(getEntityContext(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. */ function useEntityProp(kind, name, prop, _id) { const providerId = useEntityId(kind, name); const id = _id !== null && _id !== void 0 ? _id : providerId; const { value, fullValue } = (0, _data.useSelect)(select => { const { getEntityRecord, getEditedEntityRecord } = select(_name.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 } = (0, _data.useDispatch)(_name.STORE_NAME); const setValue = (0, _element.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 {[WPBlock[], Function, Function]} The block array and setters. */ function useEntityBlockEditor(kind, name, { id: _id } = {}) { const providerId = useEntityId(kind, name); const id = _id !== null && _id !== void 0 ? _id : providerId; const { getEntityRecord, getEntityRecordEdits } = (0, _data.useSelect)(_name.STORE_NAME); const { content, editedBlocks, meta } = (0, _data.useSelect)(select => { if (!id) { return {}; } const { getEditedEntityRecord } = select(_name.STORE_NAME); const editedRecord = getEditedEntityRecord(kind, name, id); return { editedBlocks: editedRecord.blocks, content: editedRecord.content, meta: editedRecord.meta }; }, [kind, name, id]); const { __unstableCreateUndoLevel, editEntityRecord } = (0, _data.useDispatch)(_name.STORE_NAME); const blocks = (0, _element.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 = (0, _blocks2.parse)(content); parsedBlocksCache.set(cackeKey, _blocks); } return _blocks; }, [kind, name, id, editedBlocks, content, getEntityRecord, getEntityRecordEdits]); const updateFootnotes = (0, _element.useCallback)(_blocks => (0, _footnotes.updateFootnotesFromMeta)(_blocks, meta), [meta]); const onChange = (0, _element.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 = [] }) => (0, _blocks2.__unstableSerializeAndClean)(blocksForSerialization), ...updateFootnotes(newBlocks) }; editEntityRecord(kind, name, id, edits, { isCached: false, ...rest }); }, [kind, name, id, blocks, updateFootnotes, __unstableCreateUndoLevel, editEntityRecord]); const onInput = (0, _element.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]; } //# sourceMappingURL=entity-provider.js.map