@wordpress/block-library
Version:
Block library for the WordPress editor.
115 lines (106 loc) • 3.29 kB
text/typescript
import { createBlock } from '@wordpress/blocks';
import type { Block } from '@wordpress/blocks';
import { escapeAttribute, escapeEditableHTML } from '@wordpress/escape-html';
export interface HeadingData {
/** The plain text content of the heading. */
content: string;
/** The heading level. */
level: number;
/** Link to the heading. */
link: string;
}
export interface NestedHeadingData {
/** The heading content, level, and link. */
heading: HeadingData;
/** The sub-headings of this heading, if any. */
children: NestedHeadingData[] | null;
}
/**
* Takes a flat list of heading parameters and nests them based on each header's
* immediate parent's level.
*
* @param headingList The flat list of headings to nest.
*
* @return The nested list of headings.
*/
export function linearToNestedHeadingList(
headingList: HeadingData[]
): NestedHeadingData[] {
const nestedHeadingList: NestedHeadingData[] = [];
headingList.forEach( ( heading, key ) => {
if ( heading.content === '' ) {
return;
}
// Make sure we are only working with the same level as the first iteration in our set.
if ( heading.level === headingList[ 0 ].level ) {
// Check that the next iteration will return a value.
// If it does and the next level is greater than the current level,
// the next iteration becomes a child of the current iteration.
if ( headingList[ key + 1 ]?.level > heading.level ) {
// We must calculate the last index before the next iteration that
// has the same level (siblings). We then use this index to slice
// the array for use in recursion. This prevents duplicate nodes.
let endOfSlice = headingList.length;
for ( let i = key + 1; i < headingList.length; i++ ) {
if ( headingList[ i ].level === heading.level ) {
endOfSlice = i;
break;
}
}
// We found a child node: Push a new node onto the return array
// with children.
nestedHeadingList.push( {
heading,
children: linearToNestedHeadingList(
headingList.slice( key + 1, endOfSlice )
),
} );
} else {
// No child node: Push a new node onto the return array.
nestedHeadingList.push( {
heading,
children: null,
} );
}
}
} );
return nestedHeadingList;
}
/**
* Converts a nested heading list into `core/list-item` blocks, with
* sub-headings nested in a `core/list` block inside their parent item.
*
* @param nestedHeadingList The nested list of headings.
* @param ordered Whether nested lists are ordered.
*
* @return The list item blocks.
*/
export function createListItemBlocks(
nestedHeadingList: NestedHeadingData[],
ordered: boolean
): Block[] {
return nestedHeadingList.map(
( { heading: { content, link }, children } ) =>
createBlock(
'core/list-item',
{
// Headings are plain text, and links can carry query args on
// paginated posts, so both need escaping to become markup.
content: link
? `<a href="${ escapeAttribute(
link
) }">${ escapeEditableHTML( content ) }</a>`
: escapeEditableHTML( content ),
},
children?.length
? [
createBlock(
'core/list',
{ ordered },
createListItemBlocks( children, ordered )
),
]
: []
)
);
}