From ce1ac10bf46fdc5157f7d46f92e9900b1c960381 Mon Sep 17 00:00:00 2001 From: Zebulan Stanphill Date: Tue, 26 May 2020 18:38:58 -0500 Subject: [PATCH] Lots of improvements. --- .../block-library/src/heading/editor.scss | 2 +- .../src/heading/heading-level-checker.js | 115 +++++++++--------- 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/packages/block-library/src/heading/editor.scss b/packages/block-library/src/heading/editor.scss index 05040868113b61..0add23862762fe 100644 --- a/packages/block-library/src/heading/editor.scss +++ b/packages/block-library/src/heading/editor.scss @@ -13,6 +13,6 @@ border: none; } -.block-library-heading__heading-level-checker { +.block-library-heading__heading-level-checker-warning { margin: 0; } diff --git a/packages/block-library/src/heading/heading-level-checker.js b/packages/block-library/src/heading/heading-level-checker.js index a206c3251abc96..75ae039eca1294 100644 --- a/packages/block-library/src/heading/heading-level-checker.js +++ b/packages/block-library/src/heading/heading-level-checker.js @@ -1,19 +1,22 @@ /** * External dependencies */ -import { countBy, flatMap, get } from 'lodash'; +import { flatMap } from 'lodash'; /** * WordPress dependencies */ import { speak } from '@wordpress/a11y'; -import { __ } from '@wordpress/i18n'; import { Notice } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; -import { compose } from '@wordpress/compose'; -import { withSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +const INVALID_LEVEL_MESSAGE = __( + 'The selected heading level may be invalid. See the content structure tool for more info.' +); -// copy from packages/editor/src/components/document-outline/index.js +// Copied from packages/editor/src/components/document-outline/index.js. /** * Returns an array of heading blocks enhanced with the following properties: * path - An array of blocks that are ancestors of the heading starting from a top-level node. @@ -26,7 +29,8 @@ import { withSelect } from '@wordpress/data'; * * @return {Array} An array of heading blocks enhanced with the properties described above. */ -export const computeOutlineHeadings = ( blocks = [], path = [] ) => { +function computeOutlineHeadings( blocks = [], path = [] ) { + // We don't polyfill native JS [].flatMap yet, so we have to use Lodash. return flatMap( blocks, ( block = {} ) => { if ( block.name === 'core/heading' ) { return { @@ -37,71 +41,70 @@ export const computeOutlineHeadings = ( blocks = [], path = [] ) => { } return computeOutlineHeadings( block.innerBlocks, [ ...path, block ] ); } ); -}; +} -export const HeadingLevelChecker = ( { - blocks = [], - title, - isTitleSupported, - selectedHeadingId, -} ) => { - const headings = computeOutlineHeadings( blocks ); +export default function HeadingLevelChecker( { selectedHeadingId } ) { + const { headings, titleIsNotEmpty, isTitleSupported } = useSelect( + ( select ) => { + const { getPostType } = select( 'core' ); + const { getBlocks } = select( 'core/block-editor' ); + const { getEditedPostAttribute } = select( 'core/editor' ); + const postType = getPostType( getEditedPostAttribute( 'type' ) ); - // Iterate headings to find prevHeadingLevel and selectedLevel - let prevHeadingLevel = 1; + return { + headings: computeOutlineHeadings( getBlocks() ?? [] ), + titleIsNotEmpty: !! getEditedPostAttribute( 'title' ), + isTitleSupported: postType?.supports?.title ?? false, + }; + }, + [] + ); + + // Find the heading level of the current block and the level of the closest + // heading preceding it. + let prevLevel = 1; let selectedLevel = 1; - let i = 0; - for ( i = 0; i < headings.length; i++ ) { + for ( let i = 0; i < headings.length; i++ ) { if ( headings[ i ].clientId === selectedHeadingId ) { selectedLevel = headings[ i ].level; if ( i >= 1 ) { - prevHeadingLevel = headings[ i - 1 ].level; + prevLevel = headings[ i - 1 ].level; } } } - const titleNode = document.querySelector( '.editor-post-title__input' ); - const hasTitle = isTitleSupported && title && titleNode; - const countByLevel = countBy( headings, 'level' ); - const hasMultipleH1 = countByLevel[ 1 ] > 1; - const isIncorrectLevel = selectedLevel > prevHeadingLevel + 1; + const titleNode = document.getElementsByClassName( + 'editor-post-title__input' + )[ 0 ]; + const hasTitle = isTitleSupported && titleIsNotEmpty && titleNode; + const hasMultipleH1 = + headings.filter( ( { level } ) => level === 1 ).length > 1; + const levelIsDuplicateH1 = hasMultipleH1 && selectedLevel === 1; + const levelAndPostTitleAreBothH1 = + selectedLevel === 1 && hasTitle && ! hasMultipleH1; + const levelIsTooDeep = selectedLevel > prevLevel + 1; + const levelIsInvalid = + levelIsDuplicateH1 || levelAndPostTitleAreBothH1 || levelIsTooDeep; - // For accessibility + // For accessibility, announce the invalid heading level to screen readers. + // The selectedLevel value is included in the dependency array so that the + // message will be replayed if a new level is selected, but the new level is + // still invalid. useEffect( () => { - if ( isIncorrectLevel ) speak( msg ); - }, [ isIncorrectLevel, selectedLevel ] ); + if ( levelIsInvalid ) speak( INVALID_LEVEL_MESSAGE ); + }, [ selectedLevel, levelIsInvalid ] ); - let msg = ''; - if ( isIncorrectLevel ) { - msg = __( 'This heading level is incorrect.' ); - } else if ( selectedLevel === 1 && hasMultipleH1 ) { - msg = __( 'Multiple H1 headings found.' ); - } else if ( selectedLevel === 1 && hasTitle && ! hasMultipleH1 ) { - msg = __( 'H1 is already used for the post title.' ); - } else { + if ( ! levelIsInvalid ) { return null; } return ( -
- - { msg } - -
+ + { INVALID_LEVEL_MESSAGE } + ); -}; - -export default compose( - withSelect( ( select ) => { - const { getBlocks } = select( 'core/block-editor' ); - const { getEditedPostAttribute } = select( 'core/editor' ); - const { getPostType } = select( 'core' ); - const postType = getPostType( getEditedPostAttribute( 'type' ) ); - - return { - blocks: getBlocks(), - title: getEditedPostAttribute( 'title' ), - isTitleSupported: get( postType, [ 'supports', 'title' ], false ), - }; - } ) -)( HeadingLevelChecker ); +}