diff --git a/packages/editor/src/components/document-outline/index.js b/packages/editor/src/components/document-outline/index.js index 6c498ccc79990..351d11f6d19c8 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,55 @@ 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} isShowingTemplate Indicates if a template is being edited or previewed. * - * @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 = [], isShowingTemplate = false ) => { return blocks.flatMap( ( block = {} ) => { - if ( block.name === 'core/heading' ) { + const isHeading = block.name === 'core/heading'; + const isMain = + isShowingTemplate && block.attributes?.tagName === 'main'; + + if ( isHeading ) { return { ...block, + type: 'heading', level: block.attributes.level, isEmpty: isEmptyHeading( block ), }; } - return computeOutlineHeadings( block.innerBlocks ); + + if ( isMain ) { + const children = computeOutlineElements( + block.innerBlocks || [], + isShowingTemplate + ); + return [ + { + ...block, + type: 'main', + children, + }, + // Flatten the children so that they are not nested under the main. + ...children, + ]; + } + + return computeOutlineElements( block.innerBlocks, isShowingTemplate ); } ); }; @@ -113,104 +148,145 @@ 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, isShowingTemplate } = useSelect( + ( select ) => { + const { getBlocks } = select( blockEditorStore ); + const { getEditedPostAttribute, getRenderingMode } = + select( editorStore ); + const { getPostType } = select( coreStore ); + const postType = getPostType( getEditedPostAttribute( 'type' ) ); + return { + title: getEditedPostAttribute( 'title' ), + blocks: getBlocks(), + isTitleSupported: postType?.supports?.title ?? false, + isShowingTemplate: + postType?.slug === 'wp_template' || + getRenderingMode() === 'template-locked', + }; + } + ); const prevHeadingLevelRef = useRef( 1 ); + const outlineElements = computeOutlineElements( blocks, isShowingTemplate ); + const headings = outlineElements.filter( + ( item ) => item.type === 'heading' + ); + const mainElements = outlineElements.filter( + ( item ) => item.type === 'main' + ); + const headingsByLevel = headings.reduce( ( acc, heading ) => { + acc[ heading.level ] = ( acc[ heading.level ] || 0 ) + 1; + return acc; + }, {} ); + const hasMultipleH1 = headingsByLevel[ 1 ] > 1; + + /** + * TODO: Update hasTitle to work with the iframe in the block editor. + * The titleNode is not found with the querySelector when the editor is iframed. + * See https://github.com/WordPress/gutenberg/issues/47624 + */ + const titleNode = document.querySelector( '.editor-post-title__input' ); + const hasTitle = isTitleSupported && title && titleNode; - const headings = computeOutlineHeadings( blocks ); - if ( headings.length < 1 ) { - return ( -
- + return ( +
+ { headings.length < 1 && ( + <> + +

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

+ + ) } + { isShowingTemplate && mainElements.length === 0 && (

{ __( - 'Navigate the structure of your document and address issues like empty or incorrect heading levels.' + 'The main element is missing. Select the block that contains your most important content and add the main HTML element in the Advanced panel.' ) }

-
- ); - } + ) } +