UNPKG

@wordpress/blocks

Version:
288 lines (265 loc) 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = parse; exports.normalizeRawBlock = normalizeRawBlock; exports.parseRawBlock = parseRawBlock; var _blockSerializationDefaultParser = require("@wordpress/block-serialization-default-parser"); var _autop = require("@wordpress/autop"); var _registration = require("../registration"); var _serializer = require("../serializer"); var _validation = require("../validation"); var _factory = require("../factory"); var _convertLegacyBlock = require("./convert-legacy-block"); var _serializeRawBlock = require("./serialize-raw-block"); var _getBlockAttributes = require("./get-block-attributes"); var _applyBlockDeprecatedVersions = require("./apply-block-deprecated-versions"); var _applyBuiltInValidationFixes = require("./apply-built-in-validation-fixes"); /** * WordPress dependencies */ /** * Internal dependencies */ /** * The raw structure of a block includes its attributes, inner * blocks, and inner HTML. It is important to distinguish inner blocks from * the HTML content of the block as only the latter is relevant for block * validation and edit operations. * * @typedef WPRawBlock * * @property {string=} blockName Block name * @property {Object=} attrs Block raw or comment attributes. * @property {string} innerHTML HTML content of the block. * @property {(string|null)[]} innerContent Content without inner blocks. * @property {WPRawBlock[]} innerBlocks Inner Blocks. */ /** * Fully parsed block object. * * @typedef WPBlock * * @property {string} name Block name * @property {Object} attributes Block raw or comment attributes. * @property {WPBlock[]} innerBlocks Inner Blocks. * @property {string} originalContent Original content of the block before validation fixes. * @property {boolean} isValid Whether the block is valid. * @property {Object[]} validationIssues Validation issues. * @property {WPRawBlock} [__unstableBlockSource] Un-processed original copy of block if created through parser. */ /** * @typedef {Object} ParseOptions * @property {boolean?} __unstableSkipMigrationLogs If a block is migrated from a deprecated version, skip logging the migration details. * @property {boolean?} __unstableSkipAutop Whether to skip autop when processing freeform content. */ /** * Convert legacy blocks to their canonical form. This function is used * both in the parser level for previous content and to convert such blocks * used in Custom Post Types templates. * * @param {WPRawBlock} rawBlock * * @return {WPRawBlock} The block's name and attributes, changed accordingly if a match was found */ function convertLegacyBlocks(rawBlock) { const [correctName, correctedAttributes] = (0, _convertLegacyBlock.convertLegacyBlockNameAndAttributes)(rawBlock.blockName, rawBlock.attrs); return { ...rawBlock, blockName: correctName, attrs: correctedAttributes }; } /** * Normalize the raw block by applying the fallback block name if none given, * sanitize the parsed HTML... * * @param {WPRawBlock} rawBlock The raw block object. * @param {ParseOptions?} options Extra options for handling block parsing. * * @return {WPRawBlock} The normalized block object. */ function normalizeRawBlock(rawBlock, options) { const fallbackBlockName = (0, _registration.getFreeformContentHandlerName)(); // If the grammar parsing don't produce any block name, use the freeform block. const rawBlockName = rawBlock.blockName || (0, _registration.getFreeformContentHandlerName)(); const rawAttributes = rawBlock.attrs || {}; const rawInnerBlocks = rawBlock.innerBlocks || []; let rawInnerHTML = rawBlock.innerHTML.trim(); // Fallback content may be upgraded from classic content expecting implicit // automatic paragraphs, so preserve them. Assumes wpautop is idempotent, // meaning there are no negative consequences to repeated autop calls. if (rawBlockName === fallbackBlockName && rawBlockName === 'core/freeform' && !options?.__unstableSkipAutop) { rawInnerHTML = (0, _autop.autop)(rawInnerHTML).trim(); } return { ...rawBlock, blockName: rawBlockName, attrs: rawAttributes, innerHTML: rawInnerHTML, innerBlocks: rawInnerBlocks }; } /** * Uses the "unregistered blockType" to create a block object. * * @param {WPRawBlock} rawBlock block. * * @return {WPRawBlock} The unregistered block object. */ function createMissingBlockType(rawBlock) { const unregisteredFallbackBlock = (0, _registration.getUnregisteredTypeHandlerName)() || (0, _registration.getFreeformContentHandlerName)(); // Preserve undelimited content for use by the unregistered type // handler. A block node's `innerHTML` isn't enough, as that field only // carries the block's own HTML and not its nested blocks. const originalUndelimitedContent = (0, _serializeRawBlock.serializeRawBlock)(rawBlock, { isCommentDelimited: false }); // Preserve full block content for use by the unregistered type // handler, block boundaries included. const originalContent = (0, _serializeRawBlock.serializeRawBlock)(rawBlock, { isCommentDelimited: true }); return { blockName: unregisteredFallbackBlock, attrs: { originalName: rawBlock.blockName, originalContent, originalUndelimitedContent }, innerHTML: rawBlock.blockName ? originalContent : rawBlock.innerHTML, innerBlocks: rawBlock.innerBlocks, innerContent: rawBlock.innerContent }; } /** * Validates a block and wraps with validation meta. * * The name here is regrettable but `validateBlock` is already taken. * * @param {WPBlock} unvalidatedBlock * @param {import('../registration').WPBlockType} blockType * @return {WPBlock} validated block, with auto-fixes if initially invalid */ function applyBlockValidation(unvalidatedBlock, blockType) { // Attempt to validate the block. const [isValid] = (0, _validation.validateBlock)(unvalidatedBlock, blockType); if (isValid) { return { ...unvalidatedBlock, isValid, validationIssues: [] }; } // If the block is invalid, attempt some built-in fixes // like custom classNames handling. const fixedBlock = (0, _applyBuiltInValidationFixes.applyBuiltInValidationFixes)(unvalidatedBlock, blockType); // Attempt to validate the block once again after the built-in fixes. const [isFixedValid, validationIssues] = (0, _validation.validateBlock)(fixedBlock, blockType); return { ...fixedBlock, isValid: isFixedValid, validationIssues }; } /** * Given a raw block returned by grammar parsing, returns a fully parsed block. * * @param {WPRawBlock} rawBlock The raw block object. * @param {ParseOptions} options Extra options for handling block parsing. * * @return {WPBlock | undefined} Fully parsed block. */ function parseRawBlock(rawBlock, options) { let normalizedBlock = normalizeRawBlock(rawBlock, options); // During the lifecycle of the project, we renamed some old blocks // and transformed others to new blocks. To avoid breaking existing content, // we added this function to properly parse the old content. normalizedBlock = convertLegacyBlocks(normalizedBlock); // Try finding the type for known block name. let blockType = (0, _registration.getBlockType)(normalizedBlock.blockName); // If not blockType is found for the specified name, fallback to the "unregisteredBlockType". if (!blockType) { normalizedBlock = createMissingBlockType(normalizedBlock); blockType = (0, _registration.getBlockType)(normalizedBlock.blockName); } // If it's an empty freeform block or there's no blockType (no missing block handler) // Then, just ignore the block. // It might be a good idea to throw a warning here. // TODO: I'm unsure about the unregisteredFallbackBlock check, // it might ignore some dynamic unregistered third party blocks wrongly. const isFallbackBlock = normalizedBlock.blockName === (0, _registration.getFreeformContentHandlerName)() || normalizedBlock.blockName === (0, _registration.getUnregisteredTypeHandlerName)(); if (!blockType || !normalizedBlock.innerHTML && isFallbackBlock) { return; } // Parse inner blocks recursively. const parsedInnerBlocks = normalizedBlock.innerBlocks.map(innerBlock => parseRawBlock(innerBlock, options)) // See https://github.com/WordPress/gutenberg/pull/17164. .filter(innerBlock => !!innerBlock); // Get the fully parsed block. const parsedBlock = (0, _factory.createBlock)(normalizedBlock.blockName, (0, _getBlockAttributes.getBlockAttributes)(blockType, normalizedBlock.innerHTML, normalizedBlock.attrs), parsedInnerBlocks); parsedBlock.originalContent = normalizedBlock.innerHTML; const validatedBlock = applyBlockValidation(parsedBlock, blockType); const { validationIssues } = validatedBlock; // Run the block deprecation and migrations. // This is performed on both invalid and valid blocks because // migration using the `migrate` functions should run even // if the output is deemed valid. const updatedBlock = (0, _applyBlockDeprecatedVersions.applyBlockDeprecatedVersions)(validatedBlock, normalizedBlock, blockType); if (!updatedBlock.isValid) { // Preserve the original unprocessed version of the block // that we received (no fixes, no deprecations) so that // we can save it as close to exactly the same way as // we loaded it. This is important to avoid corruption // and data loss caused by block implementations trying // to process data that isn't fully recognized. updatedBlock.__unstableBlockSource = rawBlock; } if (!validatedBlock.isValid && updatedBlock.isValid && !options?.__unstableSkipMigrationLogs) { /* eslint-disable no-console */ console.groupCollapsed('Updated Block: %s', blockType.name); console.info('Block successfully updated for `%s` (%o).\n\nNew content generated by `save` function:\n\n%s\n\nContent retrieved from post body:\n\n%s', blockType.name, blockType, (0, _serializer.getSaveContent)(blockType, updatedBlock.attributes), updatedBlock.originalContent); console.groupEnd(); /* eslint-enable no-console */ } else if (!validatedBlock.isValid && !updatedBlock.isValid) { validationIssues.forEach(({ log, args }) => log(...args)); } return updatedBlock; } /** * Utilizes an optimized token-driven parser based on the Gutenberg grammar spec * defined through a parsing expression grammar to take advantage of the regular * cadence provided by block delimiters -- composed syntactically through HTML * comments -- which, given a general HTML document as an input, returns a block * list array representation. * * This is a recursive-descent parser that scans linearly once through the input * document. Instead of directly recursing it utilizes a trampoline mechanism to * prevent stack overflow. This initial pass is mainly interested in separating * and isolating the blocks serialized in the document and manifestly not in the * content within the blocks. * * @see * https://developer.wordpress.org/block-editor/packages/packages-block-serialization-default-parser/ * * @param {string} content The post content. * @param {ParseOptions} options Extra options for handling block parsing. * * @return {Array} Block list. */ function parse(content, options) { return (0, _blockSerializationDefaultParser.parse)(content).reduce((accumulator, rawBlock) => { const block = parseRawBlock(rawBlock, options); if (block) { accumulator.push(block); } return accumulator; }, []); } //# sourceMappingURL=index.js.map