UNPKG

@wordpress/blocks

Version:
377 lines (349 loc) 13.5 kB
"use strict"; 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