-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Inserter: use lighter grammar parse to check allowed status #64902
Changes from all commits
8e42420
9f497fc
cadcd81
8d78213
3a244ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ import { | |
checkAllowListRecursive, | ||
getAllPatternsDependants, | ||
getInsertBlockTypeDependants, | ||
getParsedPattern, | ||
getGrammar, | ||
} from './utils'; | ||
import { INSERTER_PATTERN_TYPES } from '../components/inserter/block-patterns-tab/utils'; | ||
import { STORE_NAME } from './constants'; | ||
|
@@ -300,10 +300,10 @@ export const hasAllowedPatterns = createRegistrySelector( ( select ) => | |
if ( ! inserter ) { | ||
return false; | ||
} | ||
const { blocks } = getParsedPattern( pattern ); | ||
const grammar = getGrammar( pattern ); | ||
return ( | ||
checkAllowListRecursive( blocks, allowedBlockTypes ) && | ||
blocks.every( ( { name: blockName } ) => | ||
checkAllowListRecursive( grammar, allowedBlockTypes ) && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's funny that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like it was used when the method was introduced - #30366. We should probably check why it was removed in case there was an issue with grammar parser. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My guess is that there was a need elsewhere for parsed content, and someone switched it to a full parse. But yes we should try to fish it out of the history. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was removed in #32376. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And it was removed to improve correctness of the block names: fallback names, deprecations that migrate to another block type etc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm now looking at the code and it's totally intentional! There is a token of type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes the goal of the parser is to represent the source document, so creating a fallback block for whitespace captures that and lets us write it back. there was considerable discussion about this a number of years ago. the suggestion in each case is just to ignore the whitespace-only fallback blocks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dmsnell do you remember why the fallback blocks with whitespace are created only on top level, but not in inner blocks? If the goal of the parser is to represent the source document well, it should also capture whitespace in a nested markup. Two nice things about using the lightweight parser for parsing patterns:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@jsnajdr are you certain what you are seeing is as you describe it? these aren't "empty blocks" so much as they are HTML content outside of a block, or "HTML soup." the whitespace isn't ignored just because it's inside of another block, but when it's HTML content inside of a block it becomes part of that block's inner HTML and inner content. in fact, this is always the case because the whitespace is just You can explore this at my parser explorer - https://dmsnell.github.io/peg-parser-explorer/ - just make sure to click the Fetch button to load the block grammar.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for clarification @dmsnell, now I fully understand what's going on. At the top level the whitespace is represented as void blocks, because there is no For inner blocks, the whitespace is in There is also |
||
grammar.every( ( { name: blockName } ) => | ||
canInsertBlockType( state, blockName, rootClientId ) | ||
) | ||
); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ import { | |
getAllPatternsDependants, | ||
getInsertBlockTypeDependants, | ||
getParsedPattern, | ||
getGrammar, | ||
} from './utils'; | ||
import { orderBy } from '../utils/sorting'; | ||
import { STORE_NAME } from './constants'; | ||
|
@@ -2376,17 +2377,27 @@ export const __experimentalGetAllowedPatterns = createRegistrySelector( | |
const { getAllPatterns } = unlock( select( STORE_NAME ) ); | ||
const patterns = getAllPatterns(); | ||
const { allowedBlockTypes } = getSettings( state ); | ||
|
||
const parsedPatterns = patterns | ||
.filter( ( { inserter = true } ) => !! inserter ) | ||
.map( getParsedPattern ); | ||
.map( ( pattern ) => { | ||
return { | ||
...pattern, | ||
get blocks() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm adding this for back compat and also for us. Eventually we should adjust the uses of this selector to not expect blocks and only parse when it's needed. |
||
return getParsedPattern( pattern ).blocks; | ||
}, | ||
}; | ||
} ); | ||
|
||
const availableParsedPatterns = parsedPatterns.filter( | ||
( { blocks } ) => | ||
checkAllowListRecursive( blocks, allowedBlockTypes ) | ||
( pattern ) => | ||
checkAllowListRecursive( | ||
getGrammar( pattern ), | ||
allowedBlockTypes | ||
) | ||
); | ||
const patternsAllowed = availableParsedPatterns.filter( | ||
( { blocks } ) => | ||
blocks.every( ( { name } ) => | ||
( pattern ) => | ||
getGrammar( pattern ).every( ( { blockName: name } ) => | ||
canInsertBlockType( state, name, rootClientId ) | ||
) | ||
); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); | ||
|
||
test.describe( 'Parsing patterns', () => { | ||
test( 'Considers a pattern with whitespace an allowed pattern', async ( { | ||
admin, | ||
editor, | ||
page, | ||
} ) => { | ||
await admin.createNewPost(); | ||
await editor.insertBlock( { | ||
name: 'core/buttons', | ||
innerBlocks: [ { name: 'core/button', attributes: { text: 'a' } } ], | ||
} ); | ||
await page.keyboard.press( 'ArrowDown' ); | ||
await page.getByLabel( 'Toggle block inserter' ).click(); | ||
|
||
await page.getByRole( 'tab', { name: 'Patterns' } ).click(); | ||
await page.evaluate( () => { | ||
window.wp.data.dispatch( 'core/block-editor' ).updateSettings( { | ||
__experimentalBlockPatterns: [ | ||
{ | ||
name: 'test/whitespace', | ||
title: 'Pattern with top-level whitespace', | ||
description: '', | ||
content: `<!-- wp:button --> | ||
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button">test</a></div> | ||
<!-- /wp:button --> | ||
|
||
<!-- wp:button --> | ||
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button">test</a></div> | ||
<!-- /wp:button -->`, | ||
}, | ||
], | ||
} ); | ||
} ); | ||
await page.fill( | ||
'role=region[name="Block Library"i] >> role=searchbox[name="Search for blocks and patterns"i]', | ||
'whitespace' | ||
); | ||
await page | ||
.locator( 'role=option[name="Pattern with top-level whitespace"i]' ) | ||
.click(); | ||
expect( await editor.getBlocks() ).toMatchObject( [ | ||
{ | ||
name: 'core/buttons', | ||
innerBlocks: [ | ||
{ name: 'core/button', attributes: { text: 'a' } }, | ||
{ name: 'core/button', attributes: { text: 'test' } }, | ||
{ name: 'core/button', attributes: { text: 'test' } }, | ||
], | ||
}, | ||
] ); | ||
} ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grammar parse is lightening fast, but probably still a good idea to cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's worth measuring the impact if possible, sometimes caching adds more overhead than the actual operation.