From e3c3caefc3070561818a357506ff9a83cd28fc77 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 31 Jul 2025 15:48:38 +0900 Subject: [PATCH 1/8] Block support: Preserve anchor value in comment delimiter --- packages/block-editor/src/hooks/anchor.js | 11 +--- .../parser/apply-built-in-validation-fixes.js | 32 +++++++++- .../blocks/src/api/parser/fix-aria-label.js | 51 --------------- .../src/api/parser/fix-global-attribute.js | 63 +++++++++++++++++++ packages/blocks/src/api/parser/test/index.js | 31 +++++++++ 5 files changed, 126 insertions(+), 62 deletions(-) delete mode 100644 packages/blocks/src/api/parser/fix-aria-label.js create mode 100644 packages/blocks/src/api/parser/fix-global-attribute.js diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index e08bea1adf78eb..70579b0390e9a6 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', + }, }; } 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 ); From f3ca9eaff3dcd1260f3b0856925f4aa47e97053e Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 31 Jul 2025 16:14:24 +0900 Subject: [PATCH 2/8] Fix some unit tests --- .../src/hooks/test/__snapshots__/anchor.native.js.snap | 2 +- test/integration/fixtures/blocks/core__group__deprecated.json | 4 ++-- .../fixtures/blocks/core__group__deprecated.serialized.html | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) 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/test/integration/fixtures/blocks/core__group__deprecated.json b/test/integration/fixtures/blocks/core__group__deprecated.json index 66ae1d8fd1d999..e9cecf1b19e4c9 100644 --- a/test/integration/fixtures/blocks/core__group__deprecated.json +++ b/test/integration/fixtures/blocks/core__group__deprecated.json @@ -5,8 +5,8 @@ "attributes": { "tagName": "div", "align": "full", - "anchor": "test-id", - "backgroundColor": "lighter-blue" + "backgroundColor": "lighter-blue", + "anchor": "test-id" }, "innerBlocks": [ { diff --git a/test/integration/fixtures/blocks/core__group__deprecated.serialized.html b/test/integration/fixtures/blocks/core__group__deprecated.serialized.html index b36427bb867648..de616e96e97370 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

From ff9b63a5b97e865794e3336296b34af8902bf462 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 31 Jul 2025 16:40:10 +0900 Subject: [PATCH 3/8] Fix one unit test --- .../src/group/test/__snapshots__/transforms.native.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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.

From 047167c8f24c29dd0bc3759231fdb383164a6ab3 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 31 Jul 2025 16:44:32 +0900 Subject: [PATCH 4/8] Don't save falsy value --- packages/block-editor/src/hooks/anchor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index 70579b0390e9a6..eb67010e165ea5 100644 --- a/packages/block-editor/src/hooks/anchor.js +++ b/packages/block-editor/src/hooks/anchor.js @@ -86,7 +86,7 @@ function BlockEditAnchorControlPure( { anchor, setAttributes } ) { onChange={ ( nextValue ) => { nextValue = nextValue.replace( ANCHOR_REGEX, '-' ); setAttributes( { - anchor: nextValue, + anchor: nextValue !== '' ? nextValue : undefined, } ); } } autoCapitalize="none" From 9e9985c719c67cf2dc7d323dc4847f166da91ef8 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 31 Jul 2025 16:55:04 +0900 Subject: [PATCH 5/8] Don't preserve empty anchor when transforming from Spacer block to Separator block --- packages/block-library/src/spacer/transforms.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, } ); }, }, From 5cca67b84cd916fefe99db9eeaaf949506aad788 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 31 Jul 2025 16:55:56 +0900 Subject: [PATCH 6/8] Don't preserve empty anchor when transforming from Separator block to Spacer block --- packages/block-library/src/separator/transforms.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, } ); }, }, From c96f0ee94990502d4c5b10f4f23ea48c3f7502fe Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 31 Jul 2025 17:09:38 +0900 Subject: [PATCH 7/8] Try to fix mobile unit test --- packages/block-library/src/list/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From fb80a2df2a5c671414ce0285125537b2c356494f Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Fri, 12 Sep 2025 22:49:31 +0900 Subject: [PATCH 8/8] Regenerate fixtures --- .../core__form__deprecated-v1-custom-value.serialized.html | 2 +- .../core__form__deprecated-v1-preset-value.serialized.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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..b9624e637cde0e 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 @@ - +