@wordpress/block-library
Version:
Block library for the WordPress editor.
208 lines (182 loc) • 7.88 kB
JavaScript
/**
* 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