@wordpress/blocks
Version:
Block API for WordPress.
377 lines (349 loc) • 13.5 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.__unstableSerializeAndClean = __unstableSerializeAndClean;
exports.default = serialize;
exports.getBlockDefaultClassName = getBlockDefaultClassName;
exports.getBlockInnerHTML = getBlockInnerHTML;
exports.getBlockMenuDefaultClassName = getBlockMenuDefaultClassName;
exports.getBlockProps = getBlockProps;
exports.getCommentAttributes = getCommentAttributes;
exports.getCommentDelimitedContent = getCommentDelimitedContent;
exports.getInnerBlocksProps = getInnerBlocksProps;
exports.getSaveContent = getSaveContent;
exports.getSaveElement = getSaveElement;
exports.serializeAttributes = serializeAttributes;
exports.serializeBlock = serializeBlock;
var _element = require("@wordpress/element");
var _hooks = require("@wordpress/hooks");
var _isShallowEqual = _interopRequireDefault(require("@wordpress/is-shallow-equal"));
var _autop = require("@wordpress/autop");
var _deprecated = _interopRequireDefault(require("@wordpress/deprecated"));
var _registration = require("./registration");
var _serializeRawBlock = require("./parser/serialize-raw-block");
var _utils = require("./utils");
var _jsxRuntime = require("react/jsx-runtime");
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
/** @typedef {import('./parser').WPBlock} WPBlock */
/**
* @typedef {Object} WPBlockSerializationOptions Serialization Options.
*
* @property {boolean} isInnerBlocks Whether we are serializing inner blocks.
*/
/**
* Returns the block's default classname from its name.
*
* @param {string} blockName The block name.
*
* @return {string} The block's default class.
*/
function getBlockDefaultClassName(blockName) {
// Generated HTML classes for blocks follow the `wp-block-{name}` nomenclature.
// Blocks provided by WordPress drop the prefixes 'core/' or 'core-' (historically used in 'core-embed/').
const className = 'wp-block-' + blockName.replace(/\//, '-').replace(/^core-/, '');
return (0, _hooks.applyFilters)('blocks.getBlockDefaultClassName', className, blockName);
}
/**
* Returns the block's default menu item classname from its name.
*
* @param {string} blockName The block name.
*
* @return {string} The block's default menu item class.
*/
function getBlockMenuDefaultClassName(blockName) {
// Generated HTML classes for blocks follow the `editor-block-list-item-{name}` nomenclature.
// Blocks provided by WordPress drop the prefixes 'core/' or 'core-' (historically used in 'core-embed/').
const className = 'editor-block-list-item-' + blockName.replace(/\//, '-').replace(/^core-/, '');
return (0, _hooks.applyFilters)('blocks.getBlockMenuDefaultClassName', className, blockName);
}
const blockPropsProvider = {};
const innerBlocksPropsProvider = {};
/**
* Call within a save function to get the props for the block wrapper.
*
* @param {Object} props Optional. Props to pass to the element.
*/
function getBlockProps(props = {}) {
const {
blockType,
attributes
} = blockPropsProvider;
return getBlockProps.skipFilters ? props : (0, _hooks.applyFilters)('blocks.getSaveContent.extraProps', {
...props
}, blockType, attributes);
}
/**
* Call within a save function to get the props for the inner blocks wrapper.
*
* @param {Object} props Optional. Props to pass to the element.
*/
function getInnerBlocksProps(props = {}) {
const {
innerBlocks
} = innerBlocksPropsProvider;
// Allow a different component to be passed to getSaveElement to handle
// inner blocks, bypassing the default serialisation.
if (!Array.isArray(innerBlocks)) {
return {
...props,
children: innerBlocks
};
}
// Value is an array of blocks, so defer to block serializer.
const html = serialize(innerBlocks, {
isInnerBlocks: true
});
// Use special-cased raw HTML tag to avoid default escaping.
const children = /*#__PURE__*/(0, _jsxRuntime.jsx)(_element.RawHTML, {
children: html
});
return {
...props,
children
};
}
/**
* Given a block type containing a save render implementation and attributes, returns the
* enhanced element to be saved or string when raw HTML expected.
*
* @param {string|Object} blockTypeOrName Block type or name.
* @param {Object} attributes Block attributes.
* @param {?Array} innerBlocks Nested blocks.
*
* @return {Object|string} Save element or raw HTML string.
*/
function getSaveElement(blockTypeOrName, attributes, innerBlocks = []) {
const blockType = (0, _utils.normalizeBlockType)(blockTypeOrName);
if (!blockType?.save) {
return null;
}
let {
save
} = blockType;
// Component classes are unsupported for save since serialization must
// occur synchronously. For improved interoperability with higher-order
// components which often return component class, emulate basic support.
if (save.prototype instanceof _element.Component) {
const instance = new save({
attributes
});
save = instance.render.bind(instance);
}
blockPropsProvider.blockType = blockType;
blockPropsProvider.attributes = attributes;
innerBlocksPropsProvider.innerBlocks = innerBlocks;
let element = save({
attributes,
innerBlocks
});
if (element !== null && typeof element === 'object' && (0, _hooks.hasFilter)('blocks.getSaveContent.extraProps') && !(blockType.apiVersion > 1)) {
/**
* Filters the props applied to the block save result element.
*
* @param {Object} props Props applied to save element.
* @param {WPBlock} blockType Block type definition.
* @param {Object} attributes Block attributes.
*/
const props = (0, _hooks.applyFilters)('blocks.getSaveContent.extraProps', {
...element.props
}, blockType, attributes);
if (!(0, _isShallowEqual.default)(props, element.props)) {
element = (0, _element.cloneElement)(element, props);
}
}
/**
* Filters the save result of a block during serialization.
*
* @param {Element} element Block save result.
* @param {WPBlock} blockType Block type definition.
* @param {Object} attributes Block attributes.
*/
return (0, _hooks.applyFilters)('blocks.getSaveElement', element, blockType, attributes);
}
/**
* Given a block type containing a save render implementation and attributes, returns the
* static markup to be saved.
*
* @param {string|Object} blockTypeOrName Block type or name.
* @param {Object} attributes Block attributes.
* @param {?Array} innerBlocks Nested blocks.
*
* @return {string} Save content.
*/
function getSaveContent(blockTypeOrName, attributes, innerBlocks) {
const blockType = (0, _utils.normalizeBlockType)(blockTypeOrName);
return (0, _element.renderToString)(getSaveElement(blockType, attributes, innerBlocks));
}
/**
* Returns attributes which are to be saved and serialized into the block
* comment delimiter.
*
* When a block exists in memory it contains as its attributes both those
* parsed the block comment delimiter _and_ those which matched from the
* contents of the block.
*
* This function returns only those attributes which are needed to persist and
* which cannot be matched from the block content.
*
* @param {Object<string,*>} blockType Block type.
* @param {Object<string,*>} attributes Attributes from in-memory block data.
*
* @return {Object<string,*>} Subset of attributes for comment serialization.
*/
function getCommentAttributes(blockType, attributes) {
var _blockType$attributes;
return Object.entries((_blockType$attributes = blockType.attributes) !== null && _blockType$attributes !== void 0 ? _blockType$attributes : {}).reduce((accumulator, [key, attributeSchema]) => {
const value = attributes[key];
// Ignore undefined values.
if (undefined === value) {
return accumulator;
}
// Ignore all attributes but the ones with an "undefined" source
// "undefined" source refers to attributes saved in the block comment.
if (attributeSchema.source !== undefined) {
return accumulator;
}
// Ignore all local attributes
if (attributeSchema.role === 'local') {
return accumulator;
}
if (attributeSchema.__experimentalRole === 'local') {
(0, _deprecated.default)('__experimentalRole attribute', {
since: '6.7',
version: '6.8',
alternative: 'role attribute',
hint: `Check the block.json of the ${blockType?.name} block.`
});
return accumulator;
}
// Ignore default value.
if ('default' in attributeSchema && JSON.stringify(attributeSchema.default) === JSON.stringify(value)) {
return accumulator;
}
// Otherwise, include in comment set.
accumulator[key] = value;
return accumulator;
}, {});
}
/**
* Given an attributes object, returns a string in the serialized attributes
* format prepared for post content.
*
* @param {Object} attributes Attributes object.
*
* @return {string} Serialized attributes.
*/
function serializeAttributes(attributes) {
return JSON.stringify(attributes)
// Don't break HTML comments.
.replace(/--/g, '\\u002d\\u002d')
// Don't break non-standard-compliant tools.
.replace(/</g, '\\u003c').replace(/>/g, '\\u003e').replace(/&/g, '\\u0026')
// Bypass server stripslashes behavior which would unescape stringify's
// escaping of quotation mark.
//
// See: https://developer.wordpress.org/reference/functions/wp_kses_stripslashes/
.replace(/\\"/g, '\\u0022');
}
/**
* Given a block object, returns the Block's Inner HTML markup.
*
* @param {Object} block Block instance.
*
* @return {string} HTML.
*/
function getBlockInnerHTML(block) {
// If block was parsed as invalid or encounters an error while generating
// save content, use original content instead to avoid content loss. If a
// block contains nested content, exempt it from this condition because we
// otherwise have no access to its original content and content loss would
// still occur.
let saveContent = block.originalContent;
if (block.isValid || block.innerBlocks.length) {
try {
saveContent = getSaveContent(block.name, block.attributes, block.innerBlocks);
} catch (error) {}
}
return saveContent;
}
/**
* Returns the content of a block, including comment delimiters.
*
* @param {string} rawBlockName Block name.
* @param {Object} attributes Block attributes.
* @param {string} content Block save content.
*
* @return {string} Comment-delimited block content.
*/
function getCommentDelimitedContent(rawBlockName, attributes, content) {
const serializedAttributes = attributes && Object.entries(attributes).length ? serializeAttributes(attributes) + ' ' : '';
// Strip core blocks of their namespace prefix.
const blockName = rawBlockName?.startsWith('core/') ? rawBlockName.slice(5) : rawBlockName;
// @todo make the `wp:` prefix potentially configurable.
if (!content) {
return `<!-- wp:${blockName} ${serializedAttributes}/-->`;
}
return `<!-- wp:${blockName} ${serializedAttributes}-->\n` + content + `\n<!-- /wp:${blockName} -->`;
}
/**
* Returns the content of a block, including comment delimiters, determining
* serialized attributes and content form from the current state of the block.
*
* @param {WPBlock} block Block instance.
* @param {WPBlockSerializationOptions} options Serialization options.
*
* @return {string} Serialized block.
*/
function serializeBlock(block, {
isInnerBlocks = false
} = {}) {
if (!block.isValid && block.__unstableBlockSource) {
return (0, _serializeRawBlock.serializeRawBlock)(block.__unstableBlockSource);
}
const blockName = block.name;
const saveContent = getBlockInnerHTML(block);
if (blockName === (0, _registration.getUnregisteredTypeHandlerName)() || !isInnerBlocks && blockName === (0, _registration.getFreeformContentHandlerName)()) {
return saveContent;
}
const blockType = (0, _registration.getBlockType)(blockName);
if (!blockType) {
return saveContent;
}
const saveAttributes = getCommentAttributes(blockType, block.attributes);
return getCommentDelimitedContent(blockName, saveAttributes, saveContent);
}
function __unstableSerializeAndClean(blocks) {
// A single unmodified default block is assumed to
// be equivalent to an empty post.
if (blocks.length === 1 && (0, _utils.isUnmodifiedDefaultBlock)(blocks[0])) {
blocks = [];
}
let content = serialize(blocks);
// For compatibility, treat a post consisting of a
// single freeform block as legacy content and apply
// pre-block-editor removep'd content formatting.
if (blocks.length === 1 && blocks[0].name === (0, _registration.getFreeformContentHandlerName)() && blocks[0].name === 'core/freeform') {
content = (0, _autop.removep)(content);
}
return content;
}
/**
* Takes a block or set of blocks and returns the serialized post content.
*
* @param {Array} blocks Block(s) to serialize.
* @param {WPBlockSerializationOptions} options Serialization options.
*
* @return {string} The post content.
*/
function serialize(blocks, options) {
const blocksArray = Array.isArray(blocks) ? blocks : [blocks];
return blocksArray.map(block => serializeBlock(block, options)).join('\n\n');
}
//# sourceMappingURL=serializer.js.map