Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add heading hierarchy checker notice #14889

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/block-library/src/heading/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Internal dependencies
*/
import HeadingToolbar from './heading-toolbar';
import HeadingChecker from './heading-checker';

/**
* WordPress dependencies
Expand All @@ -24,6 +25,7 @@ export default function HeadingEdit( {
insertBlocksAfter,
onReplace,
className,
clientId,
} ) {
const { align, content, level, placeholder } = attributes;
const tagName = 'h' + level;
Expand All @@ -37,6 +39,7 @@ export default function HeadingEdit( {
<PanelBody title={ __( 'Heading Settings' ) }>
<p>{ __( 'Level' ) }</p>
<HeadingToolbar minLevel={ 1 } maxLevel={ 7 } selectedLevel={ level } onChange={ ( newLevel ) => setAttributes( { level: newLevel } ) } />
<HeadingChecker selectedLevel={ level } selectedHeadingId={ clientId } />
<p>{ __( 'Text Alignment' ) }</p>
<AlignmentToolbar
value={ align }
Expand Down
58 changes: 58 additions & 0 deletions packages/block-library/src/heading/heading-checker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* WordPress dependencies
*/
import { speak } from '@wordpress/a11y';
import { __ } from '@wordpress/i18n';
import { Notice } from '@wordpress/components';
import { useEffect } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { withSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { computeOutlineHeadings } from '../../../editor/src/components/document-outline/index.js';
gziolo marked this conversation as resolved.
Show resolved Hide resolved

export const HeadingChecker = ( { blocks = [], selectedLevel, selectedHeadingId } ) => {
gziolo marked this conversation as resolved.
Show resolved Hide resolved
const headings = computeOutlineHeadings( blocks );

// Iterate headings to find prevHeadingLevel
let prevHeadingLevel = 1;
let i = 0;
for ( i = 0; i < headings.length; i++ ) {
if ( i >= 1 && headings[ i ].clientId === selectedHeadingId ) {
prevHeadingLevel = headings[ i - 1 ].level;
}
}

const isIncorrectLevel = ( selectedLevel === 1 || selectedLevel > prevHeadingLevel + 1 );
gziolo marked this conversation as resolved.
Show resolved Hide resolved

if ( ! isIncorrectLevel ) {
return null;
}

const msg = __( 'This heading level is incorrect. ' );

// For accessibility
useEffect( () => {
speak( __( 'This heading level is incorrect' ) );
}, [ selectedLevel ] );

return (
<div className="editor-heading-checker block-editor-heading-checker">
gziolo marked this conversation as resolved.
Show resolved Hide resolved
<Notice status="warning" isDismissible={ false }>
{ msg }
</Notice>
</div>
);
};

export default compose(
withSelect( ( select ) => {
const { getBlocks } = select( 'core/block-editor' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jorgefilipecosta - is there a way to get all heading blocks only so we didn't have to use computeOutlineHeadings in the first place?

There is getGlobalBlockCount, so something similar would be nice.

That way we could share the same selector in both places and remove this computeOutlineHeadings helper altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, headings has been computed in

export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupported, hasOutlineItemsDisabled } ) => {
const headings = computeOutlineHeadings( blocks );

I am not sure whether we could avoid computing it again.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can if we create memoized shared selector, similar to how getBlocks is cached as of today. It might be a complicated task and that's why I asked @jorgefilipecosta for help.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could create a generic selector that returns the blocks of a given type, so it is not specific to this functionality.
But I'm not sure if it would fit our needs, computeOutlineHeadings does more than just returning the heading blocks, it computes, for example, the heading path required to show the path where a heading is nested on.
I think in this specific case just the selector would work, but in the checks, we do in the Content Structure we would still need something like computeOutlineHeadings to associate a path to each heading.
Exposing a selector that does what computeOutlineHeadings does (or similar functionality with paths) seems very specific to this functionality.
I guess a possible path here is just implementing level-checker.js as part of the editor module and reusing computeOutlineHeadings there.


return {
blocks: getBlocks(),
};
} )
)( HeadingChecker );
4 changes: 4 additions & 0 deletions packages/block-library/src/heading/style.native.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
.blockText {
min-height: $min-height-heading;
}
.editor-heading-checker
.block-editor-heading-checker {
margin: 0;
}
2 changes: 1 addition & 1 deletion packages/editor/src/components/document-outline/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const multipleH1Headings = [
*
* @return {Array} An array of heading blocks enhanced with the properties described above.
*/
const computeOutlineHeadings = ( blocks = [], path = [] ) => {
export const computeOutlineHeadings = ( blocks = [], path = [] ) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It no longer should be exported:

Suggested change
export const computeOutlineHeadings = ( blocks = [], path = [] ) => {
const computeOutlineHeadings = ( blocks = [], path = [] ) => {

return flatMap( blocks, ( block = {} ) => {
if ( block.name === 'core/heading' ) {
return {
Expand Down