UNPKG

@wordpress/blocks

Version:
343 lines (284 loc) 11.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.getBlockDefaultClassName = getBlockDefaultClassName; exports.getBlockMenuDefaultClassName = getBlockMenuDefaultClassName; exports.getBlockProps = getBlockProps; exports.getSaveElement = getSaveElement; exports.getSaveContent = getSaveContent; exports.getCommentAttributes = getCommentAttributes; exports.serializeAttributes = serializeAttributes; exports.getBlockInnerHTML = getBlockInnerHTML; exports.getCommentDelimitedContent = getCommentDelimitedContent; exports.serializeBlock = serializeBlock; exports.__unstableSerializeAndClean = __unstableSerializeAndClean; exports.default = serialize; var _element = require("@wordpress/element"); var _lodash = require("lodash"); var _hooks = require("@wordpress/hooks"); var _isShallowEqual = _interopRequireDefault(require("@wordpress/is-shallow-equal")); var _autop = require("@wordpress/autop"); var _registration = require("./registration"); var _utils = require("./utils"); var _blockContentProvider = _interopRequireDefault(require("../block-content-provider")); /** * External dependencies */ /** * WordPress dependencies */ /** * Internal dependencies */ /** * @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 = {}; /** * 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 (0, _hooks.applyFilters)('blocks.getSaveContent.extraProps', { ...props }, blockType, attributes); } /** * 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); 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; let element = save({ attributes, innerBlocks }); const hasLightBlockWrapper = blockType.apiVersion > 1 || (0, _registration.hasBlockSupport)(blockType, 'lightBlockWrapper', false); if ((0, _lodash.isObject)(element) && (0, _hooks.hasFilter)('blocks.getSaveContent.extraProps') && !hasLightBlockWrapper) { /** * 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 {WPElement} element Block save result. * @param {WPBlock} blockType Block type definition. * @param {Object} attributes Block attributes. */ element = (0, _hooks.applyFilters)('blocks.getSaveElement', element, blockType, attributes); return (0, _element.createElement)(_blockContentProvider.default, { innerBlocks: innerBlocks }, element); } /** * 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) { return (0, _lodash.reduce)(blockType.attributes, (accumulator, attributeSchema, key) => { 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 default value. if ('default' in attributeSchema && attributeSchema.default === 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 = !(0, _lodash.isEmpty)(attributes) ? serializeAttributes(attributes) + ' ' : ''; // Strip core blocks of their namespace prefix. const blockName = (0, _lodash.startsWith)(rawBlockName, '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 {Object} block Block instance. * @param {WPBlockSerializationOptions} options Serialization options. * * @return {string} Serialized block. */ function serializeBlock(block, { isInnerBlocks = false } = {}) { 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); 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)()) { 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) { return (0, _lodash.castArray)(blocks).map(block => serializeBlock(block, options)).join('\n\n'); } //# sourceMappingURL=serializer.js.map