@wordpress/core-data
Version:
Access to and manipulation of core WordPress entities.
258 lines (246 loc) • 7.85 kB
JavaScript
;
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