Skip to content

Commit

Permalink
Lots of improvements.
Browse files Browse the repository at this point in the history
  • Loading branch information
ZebulanStanphill committed May 26, 2020
1 parent 85578cd commit ce1ac10
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 57 deletions.
2 changes: 1 addition & 1 deletion packages/block-library/src/heading/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
border: none;
}

.block-library-heading__heading-level-checker {
.block-library-heading__heading-level-checker-warning {
margin: 0;
}
115 changes: 59 additions & 56 deletions packages/block-library/src/heading/heading-level-checker.js
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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 {
Expand All @@ -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 (
<div className="block-library-heading__heading-level-checker">
<Notice status="warning" isDismissible={ false }>
{ msg }
</Notice>
</div>
<Notice
className="block-library-heading__heading-level-checker-warning"
isDismissible={ false }
status="warning"
>
{ INVALID_LEVEL_MESSAGE }
</Notice>
);
};

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 );
}

0 comments on commit ce1ac10

Please sign in to comment.