-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Original implementation by @Jackie6 (Rebased + major fixes/tweaks.)
Derived from #14889. Rebase and major fixes by @ZebulanStanphill. Co-authored-by: Jackie6 <Jackie6@users.noreply.github.com>
- Loading branch information
1 parent
c24576a
commit a28fa40
Showing
4 changed files
with
154 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
packages/block-library/src/heading/heading-level-checker.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { countBy, flatMap, get } from 'lodash'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { speak } from '@wordpress/a11y'; | ||
import { store as blockEditorStore } from '@wordpress/block-editor'; | ||
import { store as coreStore } from '@wordpress/core-data'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { Notice } from '@wordpress/components'; | ||
import { store as editorStore } from '@wordpress/editor'; | ||
import { useEffect } from '@wordpress/element'; | ||
import { compose } from '@wordpress/compose'; | ||
import { withSelect } from '@wordpress/data'; | ||
|
||
// copy 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. | ||
* Can be an empty array if the heading is a top-level node (is not nested inside another block). | ||
* level - An integer with the heading level. | ||
* isEmpty - Flag indicating if the heading has no content. | ||
* | ||
* @param {?Array} blocks An array of blocks. | ||
* @param {?Array} path An array of blocks that are ancestors of the blocks passed as blocks. | ||
* | ||
* @return {Array} An array of heading blocks enhanced with the properties described above. | ||
*/ | ||
export const computeOutlineHeadings = ( blocks = [], path = [] ) => { | ||
return flatMap( blocks, ( block = {} ) => { | ||
if ( block.name === 'core/heading' ) { | ||
return { | ||
...block, | ||
path, | ||
level: block.attributes.level, | ||
}; | ||
} | ||
return computeOutlineHeadings( block.innerBlocks, [ ...path, block ] ); | ||
} ); | ||
}; | ||
|
||
export const HeadingLevelChecker = ( { | ||
blocks = [], | ||
title, | ||
isTitleSupported, | ||
selectedHeadingId, | ||
} ) => { | ||
const headings = computeOutlineHeadings( blocks ); | ||
|
||
// Iterate headings to find prevHeadingLevel and selectedLevel | ||
let prevHeadingLevel = 1; | ||
let selectedLevel = 1; | ||
let i = 0; | ||
for ( i = 0; i < headings.length; i++ ) { | ||
if ( headings[ i ].clientId === selectedHeadingId ) { | ||
selectedLevel = headings[ i ].level; | ||
if ( i >= 1 ) { | ||
prevHeadingLevel = 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; | ||
|
||
// For accessibility | ||
useEffect( () => { | ||
if ( isIncorrectLevel ) speak( msg ); | ||
}, [ isIncorrectLevel, selectedLevel ] ); | ||
|
||
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 { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className="block-library-heading__heading-level-checker"> | ||
<Notice status="warning" isDismissible={ false }> | ||
{ msg } | ||
</Notice> | ||
</div> | ||
); | ||
}; | ||
|
||
export default compose( | ||
withSelect( ( select ) => { | ||
const { getBlocks } = select( blockEditorStore ); | ||
const { getEditedPostAttribute } = select( editorStore ); | ||
const { getPostType } = select( coreStore ); | ||
const postType = getPostType( getEditedPostAttribute( 'type' ) ); | ||
|
||
return { | ||
blocks: getBlocks(), | ||
title: getEditedPostAttribute( 'title' ), | ||
isTitleSupported: get( postType, [ 'supports', 'title' ], false ), | ||
}; | ||
} ) | ||
)( HeadingLevelChecker ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters