@wordpress/blocks
Version:
Block API for WordPress.
288 lines (265 loc) • 11.9 kB
JavaScript
;
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