diff --git a/packages/editor/src/components/document-outline/index.js b/packages/editor/src/components/document-outline/index.js index 6c498ccc79990..188c6b8c88776 100644 --- a/packages/editor/src/components/document-outline/index.js +++ b/packages/editor/src/components/document-outline/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import clsx from 'clsx'; + /** * WordPress dependencies */ @@ -73,25 +78,50 @@ function EmptyOutlineIllustration() { ); } +const incorrectMainTag = [ +
, + + { __( + 'Your template should have exactly one main element. Adjust the HTML element in the Advanced panel of the block.' + ) } + , +]; + /** - * Returns an array of heading blocks enhanced with the following properties: - * level - An integer with the heading level. - * isEmpty - Flag indicating if the heading has no content. + * Returns an array of heading blocks and blocks with the main tagName. * - * @param {?Array} blocks An array of blocks. + * @param {?Array} blocks An array of blocks. + * @param {boolean} isEditingTemplate Indicates if a template is being edited. * - * @return {Array} An array of heading blocks enhanced with the properties described above. + * @return {Array} An array of heading blocks and blocks with the main tagName. */ -const computeOutlineHeadings = ( blocks = [] ) => { +const computeOutlineElements = ( blocks = [], isEditingTemplate = false ) => { return blocks.flatMap( ( block = {} ) => { - if ( block.name === 'core/heading' ) { + const isHeading = block.name === 'core/heading'; + const isMain = + isEditingTemplate && block.attributes?.tagName === 'main'; + + if ( isHeading ) { return { ...block, + type: 'heading', level: block.attributes.level, isEmpty: isEmptyHeading( block ), }; } - return computeOutlineHeadings( block.innerBlocks ); + + if ( isMain ) { + return { + ...block, + type: 'main', + children: computeOutlineElements( + block.innerBlocks, + isEditingTemplate + ), + }; + } + + return computeOutlineElements( block.innerBlocks, isEditingTemplate ); } ); }; @@ -113,105 +143,182 @@ export default function DocumentOutline( { hasOutlineItemsDisabled, } ) { const { selectBlock } = useDispatch( blockEditorStore ); - const { blocks, title, isTitleSupported } = useSelect( ( select ) => { - const { getBlocks } = select( blockEditorStore ); - const { getEditedPostAttribute } = select( editorStore ); - const { getPostType } = select( coreStore ); - const postType = getPostType( getEditedPostAttribute( 'type' ) ); - - return { - title: getEditedPostAttribute( 'title' ), - blocks: getBlocks(), - isTitleSupported: postType?.supports?.title ?? false, - }; - } ); + const { blocks, title, isTitleSupported, isEditingTemplate } = useSelect( + ( select ) => { + const { getBlocks } = select( blockEditorStore ); + const { getEditedPostAttribute } = select( editorStore ); + const { getPostType } = select( coreStore ); + const postType = getPostType( getEditedPostAttribute( 'type' ) ); + return { + title: getEditedPostAttribute( 'title' ), + blocks: getBlocks(), + isTitleSupported: postType?.supports?.title ?? false, + isEditingTemplate: postType?.slug === 'wp_template', + }; + } + ); const prevHeadingLevelRef = useRef( 1 ); - const headings = computeOutlineHeadings( blocks ); - if ( headings.length < 1 ) { - return ( -
- -

- { __( - 'Navigate the structure of your document and address issues like empty or incorrect heading levels.' - ) } -

-
- ); - } + const outlineElements = computeOutlineElements( blocks, isEditingTemplate ); + const headings = outlineElements.filter( + ( item ) => item.type === 'heading' + ); + const mainElements = outlineElements.filter( + ( item ) => item.type === 'main' + ); // Not great but it's the simplest way to locate the title right now. const titleNode = document.querySelector( '.editor-post-title__input' ); const hasTitle = isTitleSupported && title && titleNode; - const countByLevel = headings.reduce( - ( acc, heading ) => ( { - ...acc, - [ heading.level ]: ( acc[ heading.level ] || 0 ) + 1, - } ), - {} - ); + + // Count the number of headings and nested headings by level, to determine if there are multiple H1s. + const countByLevel = outlineElements.reduce( ( acc, element ) => { + if ( element.type === 'heading' ) { + acc[ element.level ] = ( acc[ element.level ] || 0 ) + 1; + } + if ( + element.type === 'main' && + element.children && + element.children.length > 0 + ) { + element.children.forEach( ( child ) => { + if ( child.type === 'heading' ) { + acc[ child.level ] = ( acc[ child.level ] || 0 ) + 1; + } + } ); + } + + return acc; + }, {} ); const hasMultipleH1 = countByLevel[ 1 ] > 1; - return ( -
-