diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index b2ecc81f907136..3785db7954b62b 100644 --- a/packages/block-editor/src/hooks/anchor.js +++ b/packages/block-editor/src/hooks/anchor.js @@ -20,13 +20,6 @@ import { useBlockEditingMode } from '../components/block-editing-mode'; */ const ANCHOR_REGEX = /[\s#]/g; -const ANCHOR_SCHEMA = { - type: 'string', - source: 'attribute', - attribute: 'id', - selector: '*', -}; - /** * Filters registered block settings, extending attributes with anchor using ID * of the first node. @@ -44,7 +37,9 @@ export function addAttribute( settings ) { // Gracefully handle if settings.attributes is undefined. settings.attributes = { ...settings.attributes, - anchor: ANCHOR_SCHEMA, + anchor: { + type: 'string', + }, }; } @@ -90,7 +85,7 @@ function BlockEditAnchorControlPure( { anchor, setAttributes } ) { onChange={ ( nextValue ) => { nextValue = nextValue.replace( ANCHOR_REGEX, '-' ); setAttributes( { - anchor: nextValue, + anchor: nextValue !== '' ? nextValue : undefined, } ); } } autoCapitalize="none" diff --git a/packages/block-editor/src/hooks/test/__snapshots__/anchor.native.js.snap b/packages/block-editor/src/hooks/test/__snapshots__/anchor.native.js.snap index 03407a1fe55d85..99af763fc6594f 100644 --- a/packages/block-editor/src/hooks/test/__snapshots__/anchor.native.js.snap +++ b/packages/block-editor/src/hooks/test/__snapshots__/anchor.native.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`anchor should set the ID attribute on the block 1`] = ` -" +"

" `; diff --git a/packages/block-library/src/group/test/__snapshots__/transforms.native.js.snap b/packages/block-library/src/group/test/__snapshots__/transforms.native.js.snap index ca0bd5b36ee0c4..013d86f4867445 100644 --- a/packages/block-library/src/group/test/__snapshots__/transforms.native.js.snap +++ b/packages/block-library/src/group/test/__snapshots__/transforms.native.js.snap @@ -3,7 +3,7 @@ exports[`Group block transforms to Columns block 1`] = ` "
-
+

One.

diff --git a/packages/block-library/src/list/utils.js b/packages/block-library/src/list/utils.js index 60f3354cea426f..25d28083d405fc 100644 --- a/packages/block-library/src/list/utils.js +++ b/packages/block-library/src/list/utils.js @@ -14,7 +14,7 @@ export function createListBlockFromDOMElement( listElement ) { const type = listElement.getAttribute( 'type' ); const listAttributes = { ordered: 'OL' === listElement.tagName, - anchor: listElement.id === '' ? undefined : listElement.id, + anchor: listElement.id ? listElement.id : undefined, start: listElement.getAttribute( 'start' ) ? parseInt( listElement.getAttribute( 'start' ), 10 ) : undefined, diff --git a/packages/block-library/src/separator/transforms.js b/packages/block-library/src/separator/transforms.js index 1f40cb5dba8b5e..2457714aeab251 100644 --- a/packages/block-library/src/separator/transforms.js +++ b/packages/block-library/src/separator/transforms.js @@ -24,7 +24,7 @@ const transforms = { blocks: [ 'core/spacer' ], // Transform to Spacer. transform: ( { anchor } ) => { return createBlock( 'core/spacer', { - anchor: anchor || '', + anchor: anchor || undefined, } ); }, }, diff --git a/packages/block-library/src/spacer/transforms.js b/packages/block-library/src/spacer/transforms.js index ddd8afb2ef98d9..e730d0368148fa 100644 --- a/packages/block-library/src/spacer/transforms.js +++ b/packages/block-library/src/spacer/transforms.js @@ -10,7 +10,7 @@ const transforms = { blocks: [ 'core/separator' ], // Transform to Separator. transform: ( { anchor } ) => { return createBlock( 'core/separator', { - anchor: anchor || '', + anchor: anchor || undefined, } ); }, }, diff --git a/packages/blocks/src/api/parser/apply-built-in-validation-fixes.js b/packages/blocks/src/api/parser/apply-built-in-validation-fixes.js index 52d6583265e6b9..240b7d164c3fba 100644 --- a/packages/blocks/src/api/parser/apply-built-in-validation-fixes.js +++ b/packages/blocks/src/api/parser/apply-built-in-validation-fixes.js @@ -2,7 +2,21 @@ * Internal dependencies */ import { fixCustomClassname } from './fix-custom-classname'; -import { fixAriaLabel } from './fix-aria-label'; +import { fixGlobalAttribute } from './fix-global-attribute'; + +const ARIA_LABEL_ATTR_SCHEMA = { + type: 'string', + source: 'attribute', + selector: '[data-aria-label] > *', + attribute: 'aria-label', +}; + +const ANCHOR_ATTR_SCHEMA = { + type: 'string', + source: 'attribute', + selector: '[data-anchor] > *', + attribute: 'id', +}; /** * Attempts to fix block invalidation by applying build-in validation fixes @@ -26,10 +40,22 @@ export function applyBuiltInValidationFixes( block, blockType ) { originalContent ); // Fix block invalidation for ariaLabel attribute. - updatedBlockAttributes = fixAriaLabel( + updatedBlockAttributes = fixGlobalAttribute( updatedBlockAttributes, blockType, - originalContent + originalContent, + 'ariaLabel', + 'data-aria-label', + ARIA_LABEL_ATTR_SCHEMA + ); + // Fix block invalidation for anchor attribute. + updatedBlockAttributes = fixGlobalAttribute( + updatedBlockAttributes, + blockType, + originalContent, + 'anchor', + 'data-anchor', + ANCHOR_ATTR_SCHEMA ); return { diff --git a/packages/blocks/src/api/parser/fix-aria-label.js b/packages/blocks/src/api/parser/fix-aria-label.js deleted file mode 100644 index 79fa30c713da20..00000000000000 --- a/packages/blocks/src/api/parser/fix-aria-label.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Internal dependencies - */ -import { hasBlockSupport } from '../registration'; -import { parseWithAttributeSchema } from './get-block-attributes'; - -const ARIA_LABEL_ATTR_SCHEMA = { - type: 'string', - source: 'attribute', - selector: '[data-aria-label] > *', - attribute: 'aria-label', -}; - -/** - * Given an HTML string, returns the aria-label attribute assigned to - * the root element in the markup. - * - * @param {string} innerHTML Markup string from which to extract the aria-label. - * - * @return {string} The aria-label assigned to the root element. - */ -export function getHTMLRootElementAriaLabel( innerHTML ) { - const parsed = parseWithAttributeSchema( - `
${ innerHTML }
`, - ARIA_LABEL_ATTR_SCHEMA - ); - return parsed; -} - -/** - * Given a parsed set of block attributes, if the block supports ariaLabel - * and an aria-label attribute is found, the aria-label attribute is assigned - * to the block attributes. - * - * @param {Object} blockAttributes Original block attributes. - * @param {Object} blockType Block type settings. - * @param {string} innerHTML Original block markup. - * - * @return {Object} Filtered block attributes. - */ -export function fixAriaLabel( blockAttributes, blockType, innerHTML ) { - if ( ! hasBlockSupport( blockType, 'ariaLabel', false ) ) { - return blockAttributes; - } - const modifiedBlockAttributes = { ...blockAttributes }; - const ariaLabel = getHTMLRootElementAriaLabel( innerHTML ); - if ( ariaLabel ) { - modifiedBlockAttributes.ariaLabel = ariaLabel; - } - return modifiedBlockAttributes; -} diff --git a/packages/blocks/src/api/parser/fix-global-attribute.js b/packages/blocks/src/api/parser/fix-global-attribute.js new file mode 100644 index 00000000000000..d98f962e9e6faa --- /dev/null +++ b/packages/blocks/src/api/parser/fix-global-attribute.js @@ -0,0 +1,63 @@ +/** + * Internal dependencies + */ +import { hasBlockSupport } from '../registration'; +import { parseWithAttributeSchema } from './get-block-attributes'; + +/** + * Given an HTML string and an attribute schema, returns the specified attribute + * value from the root element in the markup. + * + * @param {string} innerHTML Markup string from which to extract the attribute. + * @param {string} dataAttribute The data attribute name to use as wrapper. + * @param {Object} attributeSchema The attribute schema configuration. + * + * @return {string} The attribute value assigned to the root element. + */ +export function getHTMLRootElement( + innerHTML, + dataAttribute, + attributeSchema +) { + const parsed = parseWithAttributeSchema( + `
${ innerHTML }
`, + attributeSchema + ); + return parsed; +} + +/** + * Given a parsed set of block attributes, if the block supports the specified attribute + * and the attribute is found in the HTML, the attribute is assigned to the block attributes. + * + * @param {Object} blockAttributes Original block attributes. + * @param {Object} blockType Block type settings. + * @param {string} innerHTML Original block markup. + * @param {string} supportKey The block support key to check and attribute key to set. + * @param {string} dataAttribute The data attribute name to use as wrapper. + * @param {Object} attributeSchema The attribute schema configuration. + * + * @return {Object} Filtered block attributes. + */ +export function fixGlobalAttribute( + blockAttributes, + blockType, + innerHTML, + supportKey, + dataAttribute, + attributeSchema +) { + if ( ! hasBlockSupport( blockType, supportKey, false ) ) { + return blockAttributes; + } + const modifiedBlockAttributes = { ...blockAttributes }; + const attributeValue = getHTMLRootElement( + innerHTML, + dataAttribute, + attributeSchema + ); + if ( attributeValue ) { + modifiedBlockAttributes[ supportKey ] = attributeValue; + } + return modifiedBlockAttributes; +} diff --git a/packages/blocks/src/api/parser/test/index.js b/packages/blocks/src/api/parser/test/index.js index 452fca329af76b..dc6d2e17aedf75 100644 --- a/packages/blocks/src/api/parser/test/index.js +++ b/packages/blocks/src/api/parser/test/index.js @@ -115,6 +115,37 @@ describe( 'block parser', () => { } ); } ); + it( 'should apply anchor block validation fixes', () => { + registerBlockType( 'core/test-block', { + ...defaultBlockSettings, + attributes: { + fruit: { + type: 'string', + source: 'text', + selector: 'div', + }, + }, + supports: { + anchor: true, + }, + save: ( { attributes } ) => ( +
{ attributes.fruit }
+ ), + } ); + + const block = parseRawBlock( { + blockName: 'core/test-block', + innerHTML: '
Bananas
', + attrs: { fruit: 'Bananas' }, + } ); + + expect( block.name ).toEqual( 'core/test-block' ); + expect( block.attributes ).toEqual( { + fruit: 'Bananas', + anchor: 'custom-anchor', + } ); + } ); + it( 'should create the requested block if it exists', () => { registerBlockType( 'core/test-block', defaultBlockSettings ); diff --git a/test/integration/fixtures/blocks/core__form__deprecated-v1-custom-value.serialized.html b/test/integration/fixtures/blocks/core__form__deprecated-v1-custom-value.serialized.html index bd0f5b9e384f8b..f748716ccd3074 100644 --- a/test/integration/fixtures/blocks/core__form__deprecated-v1-custom-value.serialized.html +++ b/test/integration/fixtures/blocks/core__form__deprecated-v1-custom-value.serialized.html @@ -1,3 +1,3 @@ - + diff --git a/test/integration/fixtures/blocks/core__form__deprecated-v1-preset-value.serialized.html b/test/integration/fixtures/blocks/core__form__deprecated-v1-preset-value.serialized.html index 1966cdcb647808..bfc9e6a8af4096 100644 --- a/test/integration/fixtures/blocks/core__form__deprecated-v1-preset-value.serialized.html +++ b/test/integration/fixtures/blocks/core__form__deprecated-v1-preset-value.serialized.html @@ -1,3 +1,3 @@ - + diff --git a/test/integration/fixtures/blocks/core__group__deprecated.serialized.html b/test/integration/fixtures/blocks/core__group__deprecated.serialized.html index 87fd7047d9f965..180e451ebd17a1 100644 --- a/test/integration/fixtures/blocks/core__group__deprecated.serialized.html +++ b/test/integration/fixtures/blocks/core__group__deprecated.serialized.html @@ -1,4 +1,4 @@ - +

test