"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