UNPKG

@wordpress/block-library

Version:
208 lines (182 loc) 7.88 kB
/** * WordPress dependencies */ import { createBlock, parse } from '@wordpress/blocks'; import { applyFilters } from '@wordpress/hooks'; /** * Convert a flat menu item structure to a nested blocks structure. * * @param {Object[]} menuItems An array of menu items. * * @return {WPBlock[]} An array of blocks. */ export default function menuItemsToBlocks(menuItems) { if (!menuItems) { return null; } const menuTree = createDataTree(menuItems); const blocks = mapMenuItemsToBlocks(menuTree); return applyFilters('blocks.navigation.__unstableMenuItemsToBlocks', blocks, menuItems); } /** * A recursive function that maps menu item nodes to blocks. * * @param {WPNavMenuItem[]} menuItems An array of WPNavMenuItem items. * @param {number} level An integer representing the nesting level. * @return {Object} Object containing innerBlocks and mapping. */ function mapMenuItemsToBlocks(menuItems) { let level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; let mapping = {}; // The menuItem should be in menu_order sort order. const sortedItems = [...menuItems].sort((a, b) => a.menu_order - b.menu_order); const innerBlocks = sortedItems.map(menuItem => { var _menuItem$children, _menuItem$children2; if (menuItem.type === 'block') { const [block] = parse(menuItem.content.raw); if (!block) { return createBlock('core/freeform', { content: menuItem.content }); } return block; } const blockType = (_menuItem$children = menuItem.children) !== null && _menuItem$children !== void 0 && _menuItem$children.length ? 'core/navigation-submenu' : 'core/navigation-link'; const attributes = menuItemToBlockAttributes(menuItem, blockType, level); // If there are children recurse to build those nested blocks. const { innerBlocks: nestedBlocks = [], // alias to avoid shadowing mapping: nestedMapping = {} // alias to avoid shadowing } = (_menuItem$children2 = menuItem.children) !== null && _menuItem$children2 !== void 0 && _menuItem$children2.length ? mapMenuItemsToBlocks(menuItem.children, level + 1) : {}; // Update parent mapping with nested mapping. mapping = { ...mapping, ...nestedMapping }; // Create block with nested "innerBlocks". const block = createBlock(blockType, attributes, nestedBlocks); // Create mapping for menuItem -> block. mapping[menuItem.id] = block.clientId; return block; }); return { innerBlocks, mapping }; } /** * A WP nav_menu_item object. * For more documentation on the individual fields present on a menu item please see: * https://core.trac.wordpress.org/browser/tags/5.7.1/src/wp-includes/nav-menu.php#L789 * * Changes made here should also be mirrored in packages/edit-navigation/src/store/utils.js. * * @typedef WPNavMenuItem * * @property {Object} title stores the raw and rendered versions of the title/label for this menu item. * @property {Array} xfn the XFN relationships expressed in the link of this menu item. * @property {Array} classes the HTML class attributes for this menu item. * @property {string} attr_title the HTML title attribute for this menu item. * @property {string} object The type of object originally represented, such as 'category', 'post', or 'attachment'. * @property {string} object_id The DB ID of the original object this menu item represents, e.g. ID for posts and term_id for categories. * @property {string} description The description of this menu item. * @property {string} url The URL to which this menu item points. * @property {string} type The family of objects originally represented, such as 'post_type' or 'taxonomy'. * @property {string} target The target attribute of the link element for this menu item. */ /** * Convert block attributes to menu item. * * @param {WPNavMenuItem} menuItem the menu item to be converted to block attributes. * @param {string} blockType The block type. * @param {number} level An integer representing the nesting level. * @return {Object} the block attributes converted from the WPNavMenuItem item. */ function menuItemToBlockAttributes(_ref, blockType, level) { var _object; let { title: menuItemTitleField, xfn, classes, // eslint-disable-next-line camelcase attr_title, object, // eslint-disable-next-line camelcase object_id, description, url, type: menuItemTypeField, target } = _ref; // For historical reasons, the `core/navigation-link` variation type is `tag` // whereas WP Core expects `post_tag` as the `object` type. // To avoid writing a block migration we perform a conversion here. // See also inverse equivalent in `blockAttributesToMenuItem`. if (object && object === 'post_tag') { object = 'tag'; } return { label: (menuItemTitleField === null || menuItemTitleField === void 0 ? void 0 : menuItemTitleField.rendered) || '', ...(((_object = object) === null || _object === void 0 ? void 0 : _object.length) && { type: object }), kind: (menuItemTypeField === null || menuItemTypeField === void 0 ? void 0 : menuItemTypeField.replace('_', '-')) || 'custom', url: url || '', ...((xfn === null || xfn === void 0 ? void 0 : xfn.length) && xfn.join(' ').trim() && { rel: xfn.join(' ').trim() }), ...((classes === null || classes === void 0 ? void 0 : classes.length) && classes.join(' ').trim() && { className: classes.join(' ').trim() }), /* eslint-disable camelcase */ ...((attr_title === null || attr_title === void 0 ? void 0 : attr_title.length) && { title: attr_title }), ...(object_id && 'custom' !== object && { id: object_id }), /* eslint-enable camelcase */ ...((description === null || description === void 0 ? void 0 : description.length) && { description }), ...(target === '_blank' && { opensInNewTab: true }), ...(blockType === 'core/navigation-submenu' && { isTopLevelItem: level === 0 }), ...(blockType === 'core/navigation-link' && { isTopLevelLink: level === 0 }) }; } /** * Creates a nested, hierarchical tree representation from unstructured data that * has an inherent relationship defined between individual items. * * For example, by default, each element in the dataset should have an `id` and * `parent` property where the `parent` property indicates a relationship between * the current item and another item with a matching `id` properties. * * This is useful for building linked lists of data from flat data structures. * * @param {Array} dataset linked data to be rearranged into a hierarchical tree based on relational fields. * @param {string} id the property which uniquely identifies each entry within the array. * @param {*} relation the property which identifies how the current item is related to other items in the data (if at all). * @return {Array} a nested array of parent/child relationships */ function createDataTree(dataset) { let id = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'id'; let relation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'parent'; const hashTable = Object.create(null); const dataTree = []; for (const data of dataset) { hashTable[data[id]] = { ...data, children: [] }; if (data[relation]) { hashTable[data[relation]] = hashTable[data[relation]] || {}; hashTable[data[relation]].children = hashTable[data[relation]].children || []; hashTable[data[relation]].children.push(hashTable[data[id]]); } else { dataTree.push(hashTable[data[id]]); } } return dataTree; } //# sourceMappingURL=menu-items-to-blocks.js.map