From 435cd7398235b347ee27854533f9e13da6d9e252 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 19 Feb 2024 15:43:55 +0100 Subject: [PATCH 01/16] Add metadata in heading transforms --- .../block-library/src/heading/transforms.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/block-library/src/heading/transforms.js b/packages/block-library/src/heading/transforms.js index a4db7884620963..729e5ea8da6941 100644 --- a/packages/block-library/src/heading/transforms.js +++ b/packages/block-library/src/heading/transforms.js @@ -15,12 +15,14 @@ const transforms = { isMultiBlock: true, blocks: [ 'core/paragraph' ], transform: ( attributes ) => - attributes.map( ( { content, anchor, align: textAlign } ) => - createBlock( 'core/heading', { - content, - anchor, - textAlign, - } ) + attributes.map( + ( { content, anchor, align: textAlign, metadata } ) => + createBlock( 'core/heading', { + content, + anchor, + textAlign, + metadata, + } ) ), }, { @@ -82,8 +84,12 @@ const transforms = { isMultiBlock: true, blocks: [ 'core/paragraph' ], transform: ( attributes ) => - attributes.map( ( { content, textAlign: align } ) => - createBlock( 'core/paragraph', { content, align } ) + attributes.map( ( { content, textAlign: align, metadata } ) => + createBlock( 'core/paragraph', { + content, + align, + metadata, + } ) ), }, ], From 7b753e1cc8b89b91187b1dedf6353636fe929efb Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 19 Feb 2024 15:44:32 +0100 Subject: [PATCH 02/16] Add metadata in button transforms --- packages/block-library/src/buttons/transforms.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/buttons/transforms.js b/packages/block-library/src/buttons/transforms.js index 3e89b4973e3728..bb77d2dac02703 100644 --- a/packages/block-library/src/buttons/transforms.js +++ b/packages/block-library/src/buttons/transforms.js @@ -33,19 +33,23 @@ const transforms = { {}, // Loop the selected buttons. buttons.map( ( attributes ) => { - const element = createElement( - document, - attributes.content - ); + const { content, metadata } = attributes; + const element = createElement( document, content ); // Remove any HTML tags. const text = element.innerText || ''; // Get first url. const link = element.querySelector( 'a' ); const url = link?.getAttribute( 'href' ); + // Adapt bindings to the button block. + if ( metadata?.bindings?.content ) { + metadata.bindings.text = metadata.bindings.content; + delete metadata.bindings.content; + } // Create singular button in the buttons block. return createBlock( 'core/button', { text, url, + metadata, } ); } ) ), From c3138e3b3c7692a1b2323ab035bb438ce8bc22a7 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 19 Feb 2024 17:16:28 +0100 Subject: [PATCH 03/16] Add heading and button transform from/to paragraph --- .../editor/various/convert-block-type.spec.js | 287 +++++++++++++++++- 1 file changed, 273 insertions(+), 14 deletions(-) diff --git a/test/e2e/specs/editor/various/convert-block-type.spec.js b/test/e2e/specs/editor/various/convert-block-type.spec.js index b26cc6124f831f..1d902e20726718 100644 --- a/test/e2e/specs/editor/various/convert-block-type.spec.js +++ b/test/e2e/specs/editor/various/convert-block-type.spec.js @@ -3,7 +3,7 @@ */ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); -test.describe( 'Code block', () => { +test.describe( 'Block transforms', () => { test.beforeEach( async ( { admin } ) => { await admin.createNewPost(); } ); @@ -12,27 +12,286 @@ test.describe( 'Code block', () => { await requestUtils.deleteAllPosts(); } ); - test( 'should convert to a preformatted block', async ( { - page, - editor, - } ) => { - const code = 'print "Hello Dolly!"'; + test.describe( 'Code block', () => { + test( 'should convert to a preformatted block', async ( { + page, + editor, + } ) => { + const code = 'print "Hello Dolly!"'; - await editor.insertBlock( { name: 'core/code' } ); - await page.keyboard.type( code ); + await editor.insertBlock( { name: 'core/code' } ); + await page.keyboard.type( code ); - // Verify the content starts out as a Code block. + // Verify the content starts out as a Code block. - await expect.poll( editor.getEditedPostContent ).toBe( ` + await expect.poll( editor.getEditedPostContent ) + .toBe( `
${ code }
` ); - await editor.transformBlockTo( 'core/preformatted' ); + await editor.transformBlockTo( 'core/preformatted' ); - // The content should now be a Preformatted block with no data loss. - await expect.poll( editor.getEditedPostContent ) - .toBe( ` + // The content should now be a Preformatted block with no data loss. + await expect.poll( editor.getEditedPostContent ) + .toBe( `
${ code }
` ); + } ); + } ); + + test.describe( 'Heading block', () => { + test.describe( 'FROM paragraph', () => { + test( 'should preserve the content', async ( { editor } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/heading' ); + const headingBlock = ( await editor.getBlocks() )[ 0 ]; + expect( headingBlock.name ).toBe( 'core/heading' ); + expect( headingBlock.attributes.content ).toBe( + 'initial content' + ); + } ); + + test( 'should preserve the text align attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + align: 'right', + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/heading' ); + const headingBlock = ( await editor.getBlocks() )[ 0 ]; + expect( headingBlock.name ).toBe( 'core/heading' ); + expect( headingBlock.attributes.textAlign ).toBe( 'right' ); + } ); + + test( 'should preserve the metadata attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + metadata: { + name: 'Custom name', + }, + }, + } ); + + await editor.transformBlockTo( 'core/heading' ); + const headingBlock = ( await editor.getBlocks() )[ 0 ]; + expect( headingBlock.name ).toBe( 'core/heading' ); + expect( headingBlock.attributes.metadata ).toMatchObject( { + name: 'Custom name', + } ); + } ); + + test( 'should preserve the block bindings', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { + key: 'custom_field', + }, + }, + }, + }, + }, + } ); + + await editor.transformBlockTo( 'core/heading' ); + const headingBlock = ( await editor.getBlocks() )[ 0 ]; + expect( headingBlock.name ).toBe( 'core/heading' ); + expect( + headingBlock.attributes.metadata.bindings + ).toMatchObject( { + content: { + source: 'core/post-meta', + args: { + key: 'custom_field', + }, + }, + } ); + } ); + } ); + + test.describe( 'TO paragraph', () => { + test( 'should preserve the content', async ( { editor } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/paragraph' ); + const paragraphBlock = ( await editor.getBlocks() )[ 0 ]; + expect( paragraphBlock.name ).toBe( 'core/paragraph' ); + expect( paragraphBlock.attributes.content ).toBe( + 'initial content' + ); + } ); + + test( 'should preserve the text align attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + textAlign: 'right', + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/paragraph' ); + const paragraphBlock = ( await editor.getBlocks() )[ 0 ]; + expect( paragraphBlock.name ).toBe( 'core/paragraph' ); + expect( paragraphBlock.attributes.align ).toBe( 'right' ); + } ); + + test( 'should preserve the metadata attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'initial content', + metadata: { + name: 'Custom name', + }, + }, + } ); + + await editor.transformBlockTo( 'core/paragraph' ); + const paragraphBlock = ( await editor.getBlocks() )[ 0 ]; + expect( paragraphBlock.name ).toBe( 'core/paragraph' ); + expect( paragraphBlock.attributes.metadata ).toMatchObject( { + name: 'Custom name', + } ); + } ); + + test( 'should preserve the block bindings', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'initial content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { + key: 'custom_field', + }, + }, + }, + }, + }, + } ); + + await editor.transformBlockTo( 'core/paragraph' ); + const paragraphBlock = ( await editor.getBlocks() )[ 0 ]; + expect( paragraphBlock.name ).toBe( 'core/paragraph' ); + expect( + paragraphBlock.attributes.metadata.bindings + ).toMatchObject( { + content: { + source: 'core/post-meta', + args: { + key: 'custom_field', + }, + }, + } ); + } ); + } ); + } ); + + test.describe( 'Button block', () => { + test.describe( 'FROM paragraph', () => { + test( 'should preserve the content', async ( { editor } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/buttons' ); + const buttonBlock = ( await editor.getBlocks() )[ 0 ] + .innerBlocks[ 0 ]; + expect( buttonBlock.name ).toBe( 'core/button' ); + expect( buttonBlock.attributes.text ).toBe( 'initial content' ); + } ); + + test( 'should preserve the metadata attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + metadata: { + name: 'Custom name', + }, + }, + } ); + + await editor.transformBlockTo( 'core/buttons' ); + const buttonBlock = ( await editor.getBlocks() )[ 0 ] + .innerBlocks[ 0 ]; + expect( buttonBlock.name ).toBe( 'core/button' ); + expect( buttonBlock.attributes.metadata ).toMatchObject( { + name: 'Custom name', + } ); + } ); + + test( 'should preserve the block bindings', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { + key: 'custom_field', + }, + }, + }, + }, + }, + } ); + + await editor.transformBlockTo( 'core/buttons' ); + const buttonBlock = ( await editor.getBlocks() )[ 0 ] + .innerBlocks[ 0 ]; + expect( buttonBlock.name ).toBe( 'core/button' ); + expect( + buttonBlock.attributes.metadata.bindings + ).toMatchObject( { + text: { + source: 'core/post-meta', + args: { + key: 'custom_field', + }, + }, + } ); + } ); + } ); } ); } ); From a7cd279c89630f05ca62980f19cc592076a2362e Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 20 Feb 2024 12:28:10 +0100 Subject: [PATCH 04/16] Revert "Add heading and button transform from/to paragraph" This reverts commit c3138e3b3c7692a1b2323ab035bb438ce8bc22a7. --- .../editor/various/convert-block-type.spec.js | 287 +----------------- 1 file changed, 14 insertions(+), 273 deletions(-) diff --git a/test/e2e/specs/editor/various/convert-block-type.spec.js b/test/e2e/specs/editor/various/convert-block-type.spec.js index 1d902e20726718..b26cc6124f831f 100644 --- a/test/e2e/specs/editor/various/convert-block-type.spec.js +++ b/test/e2e/specs/editor/various/convert-block-type.spec.js @@ -3,7 +3,7 @@ */ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); -test.describe( 'Block transforms', () => { +test.describe( 'Code block', () => { test.beforeEach( async ( { admin } ) => { await admin.createNewPost(); } ); @@ -12,286 +12,27 @@ test.describe( 'Block transforms', () => { await requestUtils.deleteAllPosts(); } ); - test.describe( 'Code block', () => { - test( 'should convert to a preformatted block', async ( { - page, - editor, - } ) => { - const code = 'print "Hello Dolly!"'; + test( 'should convert to a preformatted block', async ( { + page, + editor, + } ) => { + const code = 'print "Hello Dolly!"'; - await editor.insertBlock( { name: 'core/code' } ); - await page.keyboard.type( code ); + await editor.insertBlock( { name: 'core/code' } ); + await page.keyboard.type( code ); - // Verify the content starts out as a Code block. + // Verify the content starts out as a Code block. - await expect.poll( editor.getEditedPostContent ) - .toBe( ` + await expect.poll( editor.getEditedPostContent ).toBe( `
${ code }
` ); - await editor.transformBlockTo( 'core/preformatted' ); + await editor.transformBlockTo( 'core/preformatted' ); - // The content should now be a Preformatted block with no data loss. - await expect.poll( editor.getEditedPostContent ) - .toBe( ` + // The content should now be a Preformatted block with no data loss. + await expect.poll( editor.getEditedPostContent ) + .toBe( `
${ code }
` ); - } ); - } ); - - test.describe( 'Heading block', () => { - test.describe( 'FROM paragraph', () => { - test( 'should preserve the content', async ( { editor } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'initial content', - }, - } ); - await editor.transformBlockTo( 'core/heading' ); - const headingBlock = ( await editor.getBlocks() )[ 0 ]; - expect( headingBlock.name ).toBe( 'core/heading' ); - expect( headingBlock.attributes.content ).toBe( - 'initial content' - ); - } ); - - test( 'should preserve the text align attribute', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - align: 'right', - content: 'initial content', - }, - } ); - await editor.transformBlockTo( 'core/heading' ); - const headingBlock = ( await editor.getBlocks() )[ 0 ]; - expect( headingBlock.name ).toBe( 'core/heading' ); - expect( headingBlock.attributes.textAlign ).toBe( 'right' ); - } ); - - test( 'should preserve the metadata attribute', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'initial content', - metadata: { - name: 'Custom name', - }, - }, - } ); - - await editor.transformBlockTo( 'core/heading' ); - const headingBlock = ( await editor.getBlocks() )[ 0 ]; - expect( headingBlock.name ).toBe( 'core/heading' ); - expect( headingBlock.attributes.metadata ).toMatchObject( { - name: 'Custom name', - } ); - } ); - - test( 'should preserve the block bindings', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'initial content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'custom_field', - }, - }, - }, - }, - }, - } ); - - await editor.transformBlockTo( 'core/heading' ); - const headingBlock = ( await editor.getBlocks() )[ 0 ]; - expect( headingBlock.name ).toBe( 'core/heading' ); - expect( - headingBlock.attributes.metadata.bindings - ).toMatchObject( { - content: { - source: 'core/post-meta', - args: { - key: 'custom_field', - }, - }, - } ); - } ); - } ); - - test.describe( 'TO paragraph', () => { - test( 'should preserve the content', async ( { editor } ) => { - await editor.insertBlock( { - name: 'core/heading', - attributes: { - content: 'initial content', - }, - } ); - await editor.transformBlockTo( 'core/paragraph' ); - const paragraphBlock = ( await editor.getBlocks() )[ 0 ]; - expect( paragraphBlock.name ).toBe( 'core/paragraph' ); - expect( paragraphBlock.attributes.content ).toBe( - 'initial content' - ); - } ); - - test( 'should preserve the text align attribute', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/heading', - attributes: { - textAlign: 'right', - content: 'initial content', - }, - } ); - await editor.transformBlockTo( 'core/paragraph' ); - const paragraphBlock = ( await editor.getBlocks() )[ 0 ]; - expect( paragraphBlock.name ).toBe( 'core/paragraph' ); - expect( paragraphBlock.attributes.align ).toBe( 'right' ); - } ); - - test( 'should preserve the metadata attribute', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/heading', - attributes: { - content: 'initial content', - metadata: { - name: 'Custom name', - }, - }, - } ); - - await editor.transformBlockTo( 'core/paragraph' ); - const paragraphBlock = ( await editor.getBlocks() )[ 0 ]; - expect( paragraphBlock.name ).toBe( 'core/paragraph' ); - expect( paragraphBlock.attributes.metadata ).toMatchObject( { - name: 'Custom name', - } ); - } ); - - test( 'should preserve the block bindings', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/heading', - attributes: { - content: 'initial content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'custom_field', - }, - }, - }, - }, - }, - } ); - - await editor.transformBlockTo( 'core/paragraph' ); - const paragraphBlock = ( await editor.getBlocks() )[ 0 ]; - expect( paragraphBlock.name ).toBe( 'core/paragraph' ); - expect( - paragraphBlock.attributes.metadata.bindings - ).toMatchObject( { - content: { - source: 'core/post-meta', - args: { - key: 'custom_field', - }, - }, - } ); - } ); - } ); - } ); - - test.describe( 'Button block', () => { - test.describe( 'FROM paragraph', () => { - test( 'should preserve the content', async ( { editor } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'initial content', - }, - } ); - await editor.transformBlockTo( 'core/buttons' ); - const buttonBlock = ( await editor.getBlocks() )[ 0 ] - .innerBlocks[ 0 ]; - expect( buttonBlock.name ).toBe( 'core/button' ); - expect( buttonBlock.attributes.text ).toBe( 'initial content' ); - } ); - - test( 'should preserve the metadata attribute', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'initial content', - metadata: { - name: 'Custom name', - }, - }, - } ); - - await editor.transformBlockTo( 'core/buttons' ); - const buttonBlock = ( await editor.getBlocks() )[ 0 ] - .innerBlocks[ 0 ]; - expect( buttonBlock.name ).toBe( 'core/button' ); - expect( buttonBlock.attributes.metadata ).toMatchObject( { - name: 'Custom name', - } ); - } ); - - test( 'should preserve the block bindings', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'initial content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'custom_field', - }, - }, - }, - }, - }, - } ); - - await editor.transformBlockTo( 'core/buttons' ); - const buttonBlock = ( await editor.getBlocks() )[ 0 ] - .innerBlocks[ 0 ]; - expect( buttonBlock.name ).toBe( 'core/button' ); - expect( - buttonBlock.attributes.metadata.bindings - ).toMatchObject( { - text: { - source: 'core/post-meta', - args: { - key: 'custom_field', - }, - }, - } ); - } ); - } ); } ); } ); From a6fa4ce8623caf8a1541fcc6db35b6ba6f1d49eb Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 20 Feb 2024 12:28:53 +0100 Subject: [PATCH 05/16] Move tests to individual block specs --- test/e2e/specs/editor/blocks/buttons.spec.js | 76 ++++++++ test/e2e/specs/editor/blocks/heading.spec.js | 180 +++++++++++++++++++ 2 files changed, 256 insertions(+) diff --git a/test/e2e/specs/editor/blocks/buttons.spec.js b/test/e2e/specs/editor/blocks/buttons.spec.js index dcddfca2b5b284..f62732470d9747 100644 --- a/test/e2e/specs/editor/blocks/buttons.spec.js +++ b/test/e2e/specs/editor/blocks/buttons.spec.js @@ -405,4 +405,80 @@ test.describe( 'Buttons', () => { ` ); } ); + + test.describe( 'Block transforms', () => { + test.describe( 'FROM paragraph', () => { + test( 'should preserve the content', async ( { editor } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/buttons' ); + const buttonBlock = ( await editor.getBlocks() )[ 0 ] + .innerBlocks[ 0 ]; + expect( buttonBlock.name ).toBe( 'core/button' ); + expect( buttonBlock.attributes.text ).toBe( 'initial content' ); + } ); + + test( 'should preserve the metadata attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + metadata: { + name: 'Custom name', + }, + }, + } ); + + await editor.transformBlockTo( 'core/buttons' ); + const buttonBlock = ( await editor.getBlocks() )[ 0 ] + .innerBlocks[ 0 ]; + expect( buttonBlock.name ).toBe( 'core/button' ); + expect( buttonBlock.attributes.metadata ).toMatchObject( { + name: 'Custom name', + } ); + } ); + + test( 'should preserve the block bindings', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { + key: 'custom_field', + }, + }, + }, + }, + }, + } ); + + await editor.transformBlockTo( 'core/buttons' ); + const buttonBlock = ( await editor.getBlocks() )[ 0 ] + .innerBlocks[ 0 ]; + expect( buttonBlock.name ).toBe( 'core/button' ); + expect( + buttonBlock.attributes.metadata.bindings + ).toMatchObject( { + text: { + source: 'core/post-meta', + args: { + key: 'custom_field', + }, + }, + } ); + } ); + } ); + } ); } ); diff --git a/test/e2e/specs/editor/blocks/heading.spec.js b/test/e2e/specs/editor/blocks/heading.spec.js index 705bce2c3f2c9a..f0271a8f6e8978 100644 --- a/test/e2e/specs/editor/blocks/heading.spec.js +++ b/test/e2e/specs/editor/blocks/heading.spec.js @@ -291,4 +291,184 @@ test.describe( 'Heading', () => { }, ] ); } ); + + test.describe( 'Block transforms', () => { + test.describe( 'FROM paragraph', () => { + test( 'should preserve the content', async ( { editor } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/heading' ); + const headingBlock = ( await editor.getBlocks() )[ 0 ]; + expect( headingBlock.name ).toBe( 'core/heading' ); + expect( headingBlock.attributes.content ).toBe( + 'initial content' + ); + } ); + + test( 'should preserve the text align attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + align: 'right', + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/heading' ); + const headingBlock = ( await editor.getBlocks() )[ 0 ]; + expect( headingBlock.name ).toBe( 'core/heading' ); + expect( headingBlock.attributes.textAlign ).toBe( 'right' ); + } ); + + test( 'should preserve the metadata attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + metadata: { + name: 'Custom name', + }, + }, + } ); + + await editor.transformBlockTo( 'core/heading' ); + const headingBlock = ( await editor.getBlocks() )[ 0 ]; + expect( headingBlock.name ).toBe( 'core/heading' ); + expect( headingBlock.attributes.metadata ).toMatchObject( { + name: 'Custom name', + } ); + } ); + + test( 'should preserve the block bindings', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { + key: 'custom_field', + }, + }, + }, + }, + }, + } ); + + await editor.transformBlockTo( 'core/heading' ); + const headingBlock = ( await editor.getBlocks() )[ 0 ]; + expect( headingBlock.name ).toBe( 'core/heading' ); + expect( + headingBlock.attributes.metadata.bindings + ).toMatchObject( { + content: { + source: 'core/post-meta', + args: { + key: 'custom_field', + }, + }, + } ); + } ); + } ); + + test.describe( 'TO paragraph', () => { + test( 'should preserve the content', async ( { editor } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/paragraph' ); + const paragraphBlock = ( await editor.getBlocks() )[ 0 ]; + expect( paragraphBlock.name ).toBe( 'core/paragraph' ); + expect( paragraphBlock.attributes.content ).toBe( + 'initial content' + ); + } ); + + test( 'should preserve the text align attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + textAlign: 'right', + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/paragraph' ); + const paragraphBlock = ( await editor.getBlocks() )[ 0 ]; + expect( paragraphBlock.name ).toBe( 'core/paragraph' ); + expect( paragraphBlock.attributes.align ).toBe( 'right' ); + } ); + + test( 'should preserve the metadata attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'initial content', + metadata: { + name: 'Custom name', + }, + }, + } ); + + await editor.transformBlockTo( 'core/paragraph' ); + const paragraphBlock = ( await editor.getBlocks() )[ 0 ]; + expect( paragraphBlock.name ).toBe( 'core/paragraph' ); + expect( paragraphBlock.attributes.metadata ).toMatchObject( { + name: 'Custom name', + } ); + } ); + + test( 'should preserve the block bindings', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'initial content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { + key: 'custom_field', + }, + }, + }, + }, + }, + } ); + + await editor.transformBlockTo( 'core/paragraph' ); + const paragraphBlock = ( await editor.getBlocks() )[ 0 ]; + expect( paragraphBlock.name ).toBe( 'core/paragraph' ); + expect( + paragraphBlock.attributes.metadata.bindings + ).toMatchObject( { + content: { + source: 'core/post-meta', + args: { + key: 'custom_field', + }, + }, + } ); + } ); + } ); + } ); } ); From 51689629dce80e92aa455a6c88207ad8b0202650 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 20 Feb 2024 13:52:45 +0100 Subject: [PATCH 06/16] Explicitly transform metadata in button block --- .../block-library/src/buttons/transforms.js | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/buttons/transforms.js b/packages/block-library/src/buttons/transforms.js index bb77d2dac02703..42dd0e5accc1d5 100644 --- a/packages/block-library/src/buttons/transforms.js +++ b/packages/block-library/src/buttons/transforms.js @@ -40,16 +40,29 @@ const transforms = { // Get first url. const link = element.querySelector( 'a' ); const url = link?.getAttribute( 'href' ); - // Adapt bindings to the button block. - if ( metadata?.bindings?.content ) { - metadata.bindings.text = metadata.bindings.content; - delete metadata.bindings.content; + // Transform metadata object for button block. + let buttonMetadata; + if ( metadata ) { + // Only transform these metadata props. + const supportedProps = [ 'id', 'name', 'bindings' ]; + buttonMetadata = Object.entries( metadata ).reduce( + ( obj, [ prop, value ] ) => { + if ( supportedProps.includes( prop ) ) { + obj[ prop ] = + prop === 'bindings' + ? { text: value.content } + : value; + } + return obj; + }, + {} + ); } // Create singular button in the buttons block. return createBlock( 'core/button', { text, url, - metadata, + metadata: buttonMetadata, } ); } ) ), From e472ebfc0d16b234bb4a567c057a8e330fdd8eef Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 20 Feb 2024 16:24:37 +0100 Subject: [PATCH 07/16] Explicitly transform metadata in heading block --- .../block-library/src/heading/transforms.js | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/block-library/src/heading/transforms.js b/packages/block-library/src/heading/transforms.js index 729e5ea8da6941..8b2381a0cfcc8e 100644 --- a/packages/block-library/src/heading/transforms.js +++ b/packages/block-library/src/heading/transforms.js @@ -16,13 +16,29 @@ const transforms = { blocks: [ 'core/paragraph' ], transform: ( attributes ) => attributes.map( - ( { content, anchor, align: textAlign, metadata } ) => - createBlock( 'core/heading', { + ( { content, anchor, align: textAlign, metadata } ) => { + // Transform metadata object. + let headingMetadata; + if ( metadata ) { + // Only transform these metadata props. + const supportedProps = [ 'id', 'name', 'bindings' ]; + headingMetadata = Object.entries( metadata ).reduce( + ( obj, [ prop, value ] ) => { + if ( supportedProps.includes( prop ) ) { + obj[ prop ] = value; + } + return obj; + }, + {} + ); + } + return createBlock( 'core/heading', { content, anchor, textAlign, - metadata, - } ) + metadata: headingMetadata, + } ); + } ), }, { @@ -84,13 +100,28 @@ const transforms = { isMultiBlock: true, blocks: [ 'core/paragraph' ], transform: ( attributes ) => - attributes.map( ( { content, textAlign: align, metadata } ) => - createBlock( 'core/paragraph', { + attributes.map( ( { content, textAlign: align, metadata } ) => { + // Transform metadata object. + let headingMetadata; + if ( metadata ) { + // Only transform these metadata props. + const supportedProps = [ 'id', 'name', 'bindings' ]; + headingMetadata = Object.entries( metadata ).reduce( + ( obj, [ prop, value ] ) => { + if ( supportedProps.includes( prop ) ) { + obj[ prop ] = value; + } + return obj; + }, + {} + ); + } + return createBlock( 'core/paragraph', { content, align, - metadata, - } ) - ), + metadata: headingMetadata, + } ); + } ), }, ], }; From e334ecde20bd46ec836e75e88c251f97e8c71892 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 20 Feb 2024 16:31:29 +0100 Subject: [PATCH 08/16] Make bindings transform explicit in heading block --- packages/block-library/src/heading/transforms.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/heading/transforms.js b/packages/block-library/src/heading/transforms.js index 8b2381a0cfcc8e..ad7f800ef79267 100644 --- a/packages/block-library/src/heading/transforms.js +++ b/packages/block-library/src/heading/transforms.js @@ -25,7 +25,10 @@ const transforms = { headingMetadata = Object.entries( metadata ).reduce( ( obj, [ prop, value ] ) => { if ( supportedProps.includes( prop ) ) { - obj[ prop ] = value; + obj[ prop ] = + prop === 'bindings' + ? { content: value.content } + : value; } return obj; }, @@ -109,7 +112,10 @@ const transforms = { headingMetadata = Object.entries( metadata ).reduce( ( obj, [ prop, value ] ) => { if ( supportedProps.includes( prop ) ) { - obj[ prop ] = value; + obj[ prop ] = + prop === 'bindings' + ? { content: value.content } + : value; } return obj; }, From bb3b32565f9d1ea8009dabcd2632e65935a434b4 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 20 Feb 2024 17:58:34 +0100 Subject: [PATCH 09/16] Use util to transform metadata --- packages/block-editor/src/private-apis.js | 2 + .../src/utils/get-transformed-metadata.js | 42 ++++++++++++ .../block-library/src/buttons/transforms.js | 31 ++++----- .../block-library/src/heading/transforms.js | 67 ++++++------------- 4 files changed, 77 insertions(+), 65 deletions(-) create mode 100644 packages/block-editor/src/utils/get-transformed-metadata.js diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index 220aa5f4127270..514df043ce94d0 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -11,6 +11,7 @@ import { default as PrivateQuickInserter } from './components/inserter/quick-ins import { PrivateListView } from './components/list-view'; import BlockInfo from './components/block-info-slot-fill'; import { useCanBlockToolbarBeFocused } from './utils/use-can-block-toolbar-be-focused'; +import { getTransformedMetadata } from './utils/get-transformed-metadata'; import { cleanEmptyObject, useStyleOverride } from './hooks/utils'; import BlockQuickNavigation from './components/block-quick-navigation'; import { LayoutStyle } from './components/block-list/layout'; @@ -44,6 +45,7 @@ lock( privateApis, { ResizableBoxPopover, BlockInfo, useCanBlockToolbarBeFocused, + getTransformedMetadata, cleanEmptyObject, useStyleOverride, BlockQuickNavigation, diff --git a/packages/block-editor/src/utils/get-transformed-metadata.js b/packages/block-editor/src/utils/get-transformed-metadata.js new file mode 100644 index 00000000000000..d4947502ac98ed --- /dev/null +++ b/packages/block-editor/src/utils/get-transformed-metadata.js @@ -0,0 +1,42 @@ +/** + * Transform the metadata attribute with only the values and bindings specified by each transform. + * Returns `undefined` if the input metadata is falsy. + * + * @param {Object} metadata Original metadata attribute from the block that is being transformed. + * @param {Array} supportedProps Array with the metadata properties to keep after the transform. + * @param {Object} bindingsMappings Maps each bound attribute of the original block to its corresponding attribute in the result. + * @return {Object|undefined} New metadata object only with the relevant properties. + */ +export function getTransformedMetadata( + metadata, + supportedProps, + bindingsMappings +) { + if ( ! metadata ) { + return; + } + return Object.entries( metadata ).reduce( ( obj, [ prop, value ] ) => { + // If prop is not supported, don't add it to the new metadata object. + if ( ! supportedProps.includes( prop ) ) { + return obj; + } + // If prop is `bindings` and `bindingsMappings` is not defined, don't add it to the new metadata object. + if ( prop === 'bindings' && ! bindingsMappings ) { + return obj; + } + // Adapt bindings object if `bindingsMappings` is defined. + // The rest of properties are added as they are. + obj[ prop ] = + prop === 'bindings' && !! bindingsMappings + ? Object.entries( bindingsMappings ).reduce( + ( bindingsObj, [ originalKey, resultingKey ] ) => { + bindingsObj[ resultingKey ] = value[ originalKey ]; + return bindingsObj; + }, + {} + ) + : value; + + return obj; + }, {} ); +} diff --git a/packages/block-library/src/buttons/transforms.js b/packages/block-library/src/buttons/transforms.js index 42dd0e5accc1d5..e25ebd8c6c05fe 100644 --- a/packages/block-library/src/buttons/transforms.js +++ b/packages/block-library/src/buttons/transforms.js @@ -3,6 +3,13 @@ */ import { createBlock } from '@wordpress/blocks'; import { __unstableCreateElement as createElement } from '@wordpress/rich-text'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; +/** + * Internal dependencies + */ +import { unlock } from '../lock-unlock'; + +const { getTransformedMetadata } = unlock( blockEditorPrivateApis ); const transforms = { from: [ @@ -40,29 +47,15 @@ const transforms = { // Get first url. const link = element.querySelector( 'a' ); const url = link?.getAttribute( 'href' ); - // Transform metadata object for button block. - let buttonMetadata; - if ( metadata ) { - // Only transform these metadata props. - const supportedProps = [ 'id', 'name', 'bindings' ]; - buttonMetadata = Object.entries( metadata ).reduce( - ( obj, [ prop, value ] ) => { - if ( supportedProps.includes( prop ) ) { - obj[ prop ] = - prop === 'bindings' - ? { text: value.content } - : value; - } - return obj; - }, - {} - ); - } // Create singular button in the buttons block. return createBlock( 'core/button', { text, url, - metadata: buttonMetadata, + metadata: getTransformedMetadata( + metadata, + [ 'id', 'name', 'bindings' ], + { content: 'text' } + ), } ); } ) ), diff --git a/packages/block-library/src/heading/transforms.js b/packages/block-library/src/heading/transforms.js index ad7f800ef79267..a675dcc5ca24df 100644 --- a/packages/block-library/src/heading/transforms.js +++ b/packages/block-library/src/heading/transforms.js @@ -2,11 +2,15 @@ * WordPress dependencies */ import { createBlock, getBlockAttributes } from '@wordpress/blocks'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; /** * Internal dependencies */ import { getLevelFromHeadingNodeName } from './shared'; +import { unlock } from '../lock-unlock'; + +const { getTransformedMetadata } = unlock( blockEditorPrivateApis ); const transforms = { from: [ @@ -16,32 +20,17 @@ const transforms = { blocks: [ 'core/paragraph' ], transform: ( attributes ) => attributes.map( - ( { content, anchor, align: textAlign, metadata } ) => { - // Transform metadata object. - let headingMetadata; - if ( metadata ) { - // Only transform these metadata props. - const supportedProps = [ 'id', 'name', 'bindings' ]; - headingMetadata = Object.entries( metadata ).reduce( - ( obj, [ prop, value ] ) => { - if ( supportedProps.includes( prop ) ) { - obj[ prop ] = - prop === 'bindings' - ? { content: value.content } - : value; - } - return obj; - }, - {} - ); - } - return createBlock( 'core/heading', { + ( { content, anchor, align: textAlign, metadata } ) => + createBlock( 'core/heading', { content, anchor, textAlign, - metadata: headingMetadata, - } ); - } + metadata: getTransformedMetadata( + metadata, + [ 'id', 'name', 'bindings' ], + { content: 'content' } + ), + } ) ), }, { @@ -103,31 +92,17 @@ const transforms = { isMultiBlock: true, blocks: [ 'core/paragraph' ], transform: ( attributes ) => - attributes.map( ( { content, textAlign: align, metadata } ) => { - // Transform metadata object. - let headingMetadata; - if ( metadata ) { - // Only transform these metadata props. - const supportedProps = [ 'id', 'name', 'bindings' ]; - headingMetadata = Object.entries( metadata ).reduce( - ( obj, [ prop, value ] ) => { - if ( supportedProps.includes( prop ) ) { - obj[ prop ] = - prop === 'bindings' - ? { content: value.content } - : value; - } - return obj; - }, - {} - ); - } - return createBlock( 'core/paragraph', { + attributes.map( ( { content, textAlign: align, metadata } ) => + createBlock( 'core/paragraph', { content, align, - metadata: headingMetadata, - } ); - } ), + metadata: getTransformedMetadata( + metadata, + [ 'id', 'name', 'bindings' ], + { content: 'content' } + ), + } ) + ), }, ], }; From 7eb0fdb60ecf4f1cfe2951d4c96da4aa018e666e Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 20 Feb 2024 18:00:40 +0100 Subject: [PATCH 10/16] Use metadata util in code block to keep id and name --- packages/block-library/src/code/transforms.js | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/code/transforms.js b/packages/block-library/src/code/transforms.js index af6d4686af8122..e0ba8b87f6628a 100644 --- a/packages/block-library/src/code/transforms.js +++ b/packages/block-library/src/code/transforms.js @@ -3,6 +3,14 @@ */ import { createBlock } from '@wordpress/blocks'; import { create, toHTMLString } from '@wordpress/rich-text'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { unlock } from '../lock-unlock'; + +const { getTransformedMetadata } = unlock( blockEditorPrivateApis ); const transforms = { from: [ @@ -14,17 +22,27 @@ const transforms = { { type: 'block', blocks: [ 'core/paragraph' ], - transform: ( { content } ) => - createBlock( 'core/code', { content } ), + transform: ( { content, metadata } ) => + createBlock( 'core/code', { + content, + metadata: getTransformedMetadata( metadata, [ + 'id', + 'name', + ] ), + } ), }, { type: 'block', blocks: [ 'core/html' ], - transform: ( { content: text } ) => { + transform: ( { content: text, metadata } ) => { return createBlock( 'core/code', { // The HTML is plain text (with plain line breaks), so // convert it to rich text. content: toHTMLString( { value: create( { text } ) } ), + metadata: getTransformedMetadata( metadata, [ + 'id', + 'name', + ] ), } ); }, }, @@ -51,8 +69,14 @@ const transforms = { { type: 'block', blocks: [ 'core/paragraph' ], - transform: ( { content } ) => - createBlock( 'core/paragraph', { content } ), + transform: ( { content, metadata } ) => + createBlock( 'core/paragraph', { + content, + metadata: getTransformedMetadata( metadata, [ + 'id', + 'name', + ] ), + } ), }, ], }; From b2d54b20a586fc1546e2299320112d74ebb3fc9d Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 21 Feb 2024 09:00:49 +0100 Subject: [PATCH 11/16] Move util to block-library package --- packages/block-editor/src/private-apis.js | 2 -- packages/block-library/src/buttons/transforms.js | 6 ++---- packages/block-library/src/heading/transforms.js | 5 +---- .../src/utils/get-transformed-metadata.js | 0 4 files changed, 3 insertions(+), 10 deletions(-) rename packages/{block-editor => block-library}/src/utils/get-transformed-metadata.js (100%) diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index 514df043ce94d0..220aa5f4127270 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -11,7 +11,6 @@ import { default as PrivateQuickInserter } from './components/inserter/quick-ins import { PrivateListView } from './components/list-view'; import BlockInfo from './components/block-info-slot-fill'; import { useCanBlockToolbarBeFocused } from './utils/use-can-block-toolbar-be-focused'; -import { getTransformedMetadata } from './utils/get-transformed-metadata'; import { cleanEmptyObject, useStyleOverride } from './hooks/utils'; import BlockQuickNavigation from './components/block-quick-navigation'; import { LayoutStyle } from './components/block-list/layout'; @@ -45,7 +44,6 @@ lock( privateApis, { ResizableBoxPopover, BlockInfo, useCanBlockToolbarBeFocused, - getTransformedMetadata, cleanEmptyObject, useStyleOverride, BlockQuickNavigation, diff --git a/packages/block-library/src/buttons/transforms.js b/packages/block-library/src/buttons/transforms.js index e25ebd8c6c05fe..a0ed13c686e078 100644 --- a/packages/block-library/src/buttons/transforms.js +++ b/packages/block-library/src/buttons/transforms.js @@ -3,13 +3,11 @@ */ import { createBlock } from '@wordpress/blocks'; import { __unstableCreateElement as createElement } from '@wordpress/rich-text'; -import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; + /** * Internal dependencies */ -import { unlock } from '../lock-unlock'; - -const { getTransformedMetadata } = unlock( blockEditorPrivateApis ); +import { getTransformedMetadata } from '../utils/get-transformed-metadata'; const transforms = { from: [ diff --git a/packages/block-library/src/heading/transforms.js b/packages/block-library/src/heading/transforms.js index a675dcc5ca24df..24ee9e5ffa83af 100644 --- a/packages/block-library/src/heading/transforms.js +++ b/packages/block-library/src/heading/transforms.js @@ -2,15 +2,12 @@ * WordPress dependencies */ import { createBlock, getBlockAttributes } from '@wordpress/blocks'; -import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; /** * Internal dependencies */ import { getLevelFromHeadingNodeName } from './shared'; -import { unlock } from '../lock-unlock'; - -const { getTransformedMetadata } = unlock( blockEditorPrivateApis ); +import { getTransformedMetadata } from '../utils/get-transformed-metadata'; const transforms = { from: [ diff --git a/packages/block-editor/src/utils/get-transformed-metadata.js b/packages/block-library/src/utils/get-transformed-metadata.js similarity index 100% rename from packages/block-editor/src/utils/get-transformed-metadata.js rename to packages/block-library/src/utils/get-transformed-metadata.js From aeaf571330ae381c7bc7b9313412b8ac3188bda2 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 21 Feb 2024 09:03:01 +0100 Subject: [PATCH 12/16] Change code transforms util path --- packages/block-library/src/code/transforms.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/block-library/src/code/transforms.js b/packages/block-library/src/code/transforms.js index e0ba8b87f6628a..2f1bb3f467125c 100644 --- a/packages/block-library/src/code/transforms.js +++ b/packages/block-library/src/code/transforms.js @@ -3,14 +3,11 @@ */ import { createBlock } from '@wordpress/blocks'; import { create, toHTMLString } from '@wordpress/rich-text'; -import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; /** * Internal dependencies */ -import { unlock } from '../lock-unlock'; - -const { getTransformedMetadata } = unlock( blockEditorPrivateApis ); +import { getTransformedMetadata } from '../utils/get-transformed-metadata'; const transforms = { from: [ From d32350783f828527a49b1cbf27460c920f679870 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 21 Feb 2024 10:09:16 +0100 Subject: [PATCH 13/16] Inherit transform metadata props in the util --- .../block-library/src/buttons/transforms.js | 4 +- packages/block-library/src/code/transforms.js | 27 +++--- .../block-library/src/heading/transforms.js | 8 +- .../src/utils/get-transformed-metadata.js | 91 ++++++++++++++----- 4 files changed, 88 insertions(+), 42 deletions(-) diff --git a/packages/block-library/src/buttons/transforms.js b/packages/block-library/src/buttons/transforms.js index a0ed13c686e078..c5281ed90c37e1 100644 --- a/packages/block-library/src/buttons/transforms.js +++ b/packages/block-library/src/buttons/transforms.js @@ -51,8 +51,8 @@ const transforms = { url, metadata: getTransformedMetadata( metadata, - [ 'id', 'name', 'bindings' ], - { content: 'text' } + 'core/paragraph', + 'core/button' ), } ); } ) diff --git a/packages/block-library/src/code/transforms.js b/packages/block-library/src/code/transforms.js index 2f1bb3f467125c..0e17a98644d15a 100644 --- a/packages/block-library/src/code/transforms.js +++ b/packages/block-library/src/code/transforms.js @@ -22,10 +22,11 @@ const transforms = { transform: ( { content, metadata } ) => createBlock( 'core/code', { content, - metadata: getTransformedMetadata( metadata, [ - 'id', - 'name', - ] ), + metadata: getTransformedMetadata( + metadata, + 'core/paragraph', + 'core/code' + ), } ), }, { @@ -36,10 +37,11 @@ const transforms = { // The HTML is plain text (with plain line breaks), so // convert it to rich text. content: toHTMLString( { value: create( { text } ) } ), - metadata: getTransformedMetadata( metadata, [ - 'id', - 'name', - ] ), + metadata: getTransformedMetadata( + metadata, + 'core/html', + 'core/code' + ), } ); }, }, @@ -69,10 +71,11 @@ const transforms = { transform: ( { content, metadata } ) => createBlock( 'core/paragraph', { content, - metadata: getTransformedMetadata( metadata, [ - 'id', - 'name', - ] ), + metadata: getTransformedMetadata( + metadata, + 'core/code', + 'core/paragraph' + ), } ), }, ], diff --git a/packages/block-library/src/heading/transforms.js b/packages/block-library/src/heading/transforms.js index 24ee9e5ffa83af..848c4629d8a6fb 100644 --- a/packages/block-library/src/heading/transforms.js +++ b/packages/block-library/src/heading/transforms.js @@ -24,8 +24,8 @@ const transforms = { textAlign, metadata: getTransformedMetadata( metadata, - [ 'id', 'name', 'bindings' ], - { content: 'content' } + 'core/paragraph', + 'core/heading' ), } ) ), @@ -95,8 +95,8 @@ const transforms = { align, metadata: getTransformedMetadata( metadata, - [ 'id', 'name', 'bindings' ], - { content: 'content' } + 'core/heading', + 'core/paragraph' ), } ) ), diff --git a/packages/block-library/src/utils/get-transformed-metadata.js b/packages/block-library/src/utils/get-transformed-metadata.js index d4947502ac98ed..f82372a024ad6a 100644 --- a/packages/block-library/src/utils/get-transformed-metadata.js +++ b/packages/block-library/src/utils/get-transformed-metadata.js @@ -1,41 +1,84 @@ +/** + * WordPress dependencies + */ +import { getBlockType } from '@wordpress/blocks'; + /** * Transform the metadata attribute with only the values and bindings specified by each transform. * Returns `undefined` if the input metadata is falsy. * - * @param {Object} metadata Original metadata attribute from the block that is being transformed. - * @param {Array} supportedProps Array with the metadata properties to keep after the transform. - * @param {Object} bindingsMappings Maps each bound attribute of the original block to its corresponding attribute in the result. + * @param {Object} metadata Original metadata attribute from the block that is being transformed. + * @param {Array} fromBlockName Name of the original block that is being transformed. + * @param {Object} toBlockName Name of the final block after the transformation. * @return {Object|undefined} New metadata object only with the relevant properties. */ -export function getTransformedMetadata( - metadata, - supportedProps, - bindingsMappings -) { +export function getTransformedMetadata( metadata, fromBlockName, toBlockName ) { if ( ! metadata ) { return; } + const { supports } = getBlockType( toBlockName ); + // Fixed until an opt-in mechanism is implemented. + const BLOCK_BINDINGS_SUPPORTED_BLOCKS = [ + 'core/paragraph', + 'core/heading', + 'core/image', + 'core/button', + ]; + // Fixed until a proper mechanism is defined. + const BINDINGS_ATTRIBUTES_MAPPING = { + 'core/paragraph': { + content: { + 'core/heading': 'content', + 'core/button': 'text', + }, + }, + 'core/heading': { + content: { + 'core/paragraph': 'content', + 'core/button': 'text', + }, + }, + 'core/button': { + text: { + 'core/paragraph': 'content', + 'core/heading': 'content', + }, + }, + }; + // The metadata properties that should be preserved after the transform. + const transformSupportedProps = []; + // If it support bindings, add the `id` and `bindings` properties. + if ( BLOCK_BINDINGS_SUPPORTED_BLOCKS.includes( toBlockName ) ) { + transformSupportedProps.push( 'id', 'bindings' ); + } + // If it support block naming (true by default), add the `name` property. + if ( supports.renaming !== false ) { + transformSupportedProps.push( 'name' ); + } + return Object.entries( metadata ).reduce( ( obj, [ prop, value ] ) => { // If prop is not supported, don't add it to the new metadata object. - if ( ! supportedProps.includes( prop ) ) { + if ( ! transformSupportedProps.includes( prop ) ) { return obj; } - // If prop is `bindings` and `bindingsMappings` is not defined, don't add it to the new metadata object. - if ( prop === 'bindings' && ! bindingsMappings ) { - return obj; + + if ( prop === 'bindings' ) { + // Adapt bindings object based on the mapping. + obj[ prop ] = Object.entries( value ).reduce( + ( transformedObj, [ originalKey, originalObj ] ) => { + const transformedKey = + BINDINGS_ATTRIBUTES_MAPPING[ fromBlockName ][ + originalKey + ][ toBlockName ]; + + transformedObj[ transformedKey ] = originalObj; + return transformedObj; + }, + {} + ); + } else { + obj[ prop ] = value; } - // Adapt bindings object if `bindingsMappings` is defined. - // The rest of properties are added as they are. - obj[ prop ] = - prop === 'bindings' && !! bindingsMappings - ? Object.entries( bindingsMappings ).reduce( - ( bindingsObj, [ originalKey, resultingKey ] ) => { - bindingsObj[ resultingKey ] = value[ originalKey ]; - return bindingsObj; - }, - {} - ) - : value; return obj; }, {} ); From 3ca3a08405635dd0f83a259761baac8ae94ab3fd Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 21 Feb 2024 10:20:30 +0100 Subject: [PATCH 14/16] Add transform tests for code block --- test/e2e/specs/editor/blocks/code.spec.js | 116 ++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/test/e2e/specs/editor/blocks/code.spec.js b/test/e2e/specs/editor/blocks/code.spec.js index 6abfb15d10b83b..ba5af46f69cfd5 100644 --- a/test/e2e/specs/editor/blocks/code.spec.js +++ b/test/e2e/specs/editor/blocks/code.spec.js @@ -46,4 +46,120 @@ test.describe( 'Code', () => { expect( await editor.getEditedPostContent() ).toMatchSnapshot(); } ); + + test.describe( 'Block transforms', () => { + test.describe( 'FROM paragraph', () => { + test( 'should preserve the content', async ( { editor } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/code' ); + const codeBlock = ( await editor.getBlocks() )[ 0 ]; + expect( codeBlock.name ).toBe( 'core/code' ); + expect( codeBlock.attributes.content ).toBe( + 'initial content' + ); + } ); + + test( 'should preserve the metadata name attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'initial content', + metadata: { + name: 'Custom name', + }, + }, + } ); + + await editor.transformBlockTo( 'core/code' ); + const codeBlock = ( await editor.getBlocks() )[ 0 ]; + expect( codeBlock.name ).toBe( 'core/code' ); + expect( codeBlock.attributes.metadata ).toMatchObject( { + name: 'Custom name', + } ); + } ); + } ); + + test.describe( 'FROM HTML', () => { + test( 'should preserve the content', async ( { editor } ) => { + await editor.insertBlock( { + name: 'core/html', + attributes: { + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/code' ); + const codeBlock = ( await editor.getBlocks() )[ 0 ]; + expect( codeBlock.name ).toBe( 'core/code' ); + expect( codeBlock.attributes.content ).toBe( + 'initial content' + ); + } ); + + test( 'should preserve the metadata name attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/html', + attributes: { + content: 'initial content', + metadata: { + name: 'Custom name', + }, + }, + } ); + + await editor.transformBlockTo( 'core/code' ); + const codeBlock = ( await editor.getBlocks() )[ 0 ]; + expect( codeBlock.name ).toBe( 'core/code' ); + expect( codeBlock.attributes.metadata ).toMatchObject( { + name: 'Custom name', + } ); + } ); + } ); + + test.describe( 'TO paragraph', () => { + test( 'should preserve the content', async ( { editor } ) => { + await editor.insertBlock( { + name: 'core/code', + attributes: { + content: 'initial content', + }, + } ); + await editor.transformBlockTo( 'core/paragraph' ); + const codeBlock = ( await editor.getBlocks() )[ 0 ]; + expect( codeBlock.name ).toBe( 'core/paragraph' ); + expect( codeBlock.attributes.content ).toBe( + 'initial content' + ); + } ); + + test( 'should preserve the metadata name attribute', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/code', + attributes: { + content: 'initial content', + metadata: { + name: 'Custom name', + }, + }, + } ); + + await editor.transformBlockTo( 'core/paragraph' ); + const codeBlock = ( await editor.getBlocks() )[ 0 ]; + expect( codeBlock.name ).toBe( 'core/paragraph' ); + expect( codeBlock.attributes.metadata ).toMatchObject( { + name: 'Custom name', + } ); + } ); + } ); + } ); } ); From 2d09640e3bee54fdb8893e8275d852c9a7152087 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 21 Feb 2024 10:32:45 +0100 Subject: [PATCH 15/16] Return early if no supported properties --- packages/block-library/src/utils/get-transformed-metadata.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/block-library/src/utils/get-transformed-metadata.js b/packages/block-library/src/utils/get-transformed-metadata.js index f82372a024ad6a..b9fab2f8ed87cd 100644 --- a/packages/block-library/src/utils/get-transformed-metadata.js +++ b/packages/block-library/src/utils/get-transformed-metadata.js @@ -56,6 +56,11 @@ export function getTransformedMetadata( metadata, fromBlockName, toBlockName ) { transformSupportedProps.push( 'name' ); } + // Return early if no supported properties. + if ( ! transformSupportedProps.length ) { + return; + } + return Object.entries( metadata ).reduce( ( obj, [ prop, value ] ) => { // If prop is not supported, don't add it to the new metadata object. if ( ! transformSupportedProps.includes( prop ) ) { From 5e3eda0efe3b2b2cedd2aafe078ef0a6427e6779 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 21 Feb 2024 11:32:54 +0100 Subject: [PATCH 16/16] Use bindings callback for mapping attributes --- .../block-library/src/buttons/transforms.js | 6 +- packages/block-library/src/code/transforms.js | 13 +-- .../block-library/src/heading/transforms.js | 12 ++- .../src/utils/get-transformed-metadata.js | 79 +++++++------------ 4 files changed, 41 insertions(+), 69 deletions(-) diff --git a/packages/block-library/src/buttons/transforms.js b/packages/block-library/src/buttons/transforms.js index c5281ed90c37e1..9848299f3a99f9 100644 --- a/packages/block-library/src/buttons/transforms.js +++ b/packages/block-library/src/buttons/transforms.js @@ -51,8 +51,10 @@ const transforms = { url, metadata: getTransformedMetadata( metadata, - 'core/paragraph', - 'core/button' + 'core/button', + ( { content: contentBinding } ) => ( { + text: contentBinding, + } ) ), } ); } ) diff --git a/packages/block-library/src/code/transforms.js b/packages/block-library/src/code/transforms.js index 0e17a98644d15a..e537db342b8d5c 100644 --- a/packages/block-library/src/code/transforms.js +++ b/packages/block-library/src/code/transforms.js @@ -22,11 +22,7 @@ const transforms = { transform: ( { content, metadata } ) => createBlock( 'core/code', { content, - metadata: getTransformedMetadata( - metadata, - 'core/paragraph', - 'core/code' - ), + metadata: getTransformedMetadata( metadata, 'core/code' ), } ), }, { @@ -37,11 +33,7 @@ const transforms = { // The HTML is plain text (with plain line breaks), so // convert it to rich text. content: toHTMLString( { value: create( { text } ) } ), - metadata: getTransformedMetadata( - metadata, - 'core/html', - 'core/code' - ), + metadata: getTransformedMetadata( metadata, 'core/code' ), } ); }, }, @@ -73,7 +65,6 @@ const transforms = { content, metadata: getTransformedMetadata( metadata, - 'core/code', 'core/paragraph' ), } ), diff --git a/packages/block-library/src/heading/transforms.js b/packages/block-library/src/heading/transforms.js index 848c4629d8a6fb..f040ff06e37e86 100644 --- a/packages/block-library/src/heading/transforms.js +++ b/packages/block-library/src/heading/transforms.js @@ -24,8 +24,10 @@ const transforms = { textAlign, metadata: getTransformedMetadata( metadata, - 'core/paragraph', - 'core/heading' + 'core/heading', + ( { content: contentBinding } ) => ( { + content: contentBinding, + } ) ), } ) ), @@ -95,8 +97,10 @@ const transforms = { align, metadata: getTransformedMetadata( metadata, - 'core/heading', - 'core/paragraph' + 'core/paragraph', + ( { content: contentBinding } ) => ( { + content: contentBinding, + } ) ), } ) ), diff --git a/packages/block-library/src/utils/get-transformed-metadata.js b/packages/block-library/src/utils/get-transformed-metadata.js index b9fab2f8ed87cd..53d79d3c1e42ac 100644 --- a/packages/block-library/src/utils/get-transformed-metadata.js +++ b/packages/block-library/src/utils/get-transformed-metadata.js @@ -7,16 +7,20 @@ import { getBlockType } from '@wordpress/blocks'; * Transform the metadata attribute with only the values and bindings specified by each transform. * Returns `undefined` if the input metadata is falsy. * - * @param {Object} metadata Original metadata attribute from the block that is being transformed. - * @param {Array} fromBlockName Name of the original block that is being transformed. - * @param {Object} toBlockName Name of the final block after the transformation. + * @param {Object} metadata Original metadata attribute from the block that is being transformed. + * @param {Object} newBlockName Name of the final block after the transformation. + * @param {Function} bindingsCallback Optional callback to transform the `bindings` property object. * @return {Object|undefined} New metadata object only with the relevant properties. */ -export function getTransformedMetadata( metadata, fromBlockName, toBlockName ) { +export function getTransformedMetadata( + metadata, + newBlockName, + bindingsCallback +) { if ( ! metadata ) { return; } - const { supports } = getBlockType( toBlockName ); + const { supports } = getBlockType( newBlockName ); // Fixed until an opt-in mechanism is implemented. const BLOCK_BINDINGS_SUPPORTED_BLOCKS = [ 'core/paragraph', @@ -24,31 +28,13 @@ export function getTransformedMetadata( metadata, fromBlockName, toBlockName ) { 'core/image', 'core/button', ]; - // Fixed until a proper mechanism is defined. - const BINDINGS_ATTRIBUTES_MAPPING = { - 'core/paragraph': { - content: { - 'core/heading': 'content', - 'core/button': 'text', - }, - }, - 'core/heading': { - content: { - 'core/paragraph': 'content', - 'core/button': 'text', - }, - }, - 'core/button': { - text: { - 'core/paragraph': 'content', - 'core/heading': 'content', - }, - }, - }; // The metadata properties that should be preserved after the transform. const transformSupportedProps = []; - // If it support bindings, add the `id` and `bindings` properties. - if ( BLOCK_BINDINGS_SUPPORTED_BLOCKS.includes( toBlockName ) ) { + // If it support bindings, and there is a transform bindings callback, add the `id` and `bindings` properties. + if ( + BLOCK_BINDINGS_SUPPORTED_BLOCKS.includes( newBlockName ) && + bindingsCallback + ) { transformSupportedProps.push( 'id', 'bindings' ); } // If it support block naming (true by default), add the `name` property. @@ -61,30 +47,19 @@ export function getTransformedMetadata( metadata, fromBlockName, toBlockName ) { return; } - return Object.entries( metadata ).reduce( ( obj, [ prop, value ] ) => { - // If prop is not supported, don't add it to the new metadata object. - if ( ! transformSupportedProps.includes( prop ) ) { + const newMetadata = Object.entries( metadata ).reduce( + ( obj, [ prop, value ] ) => { + // If prop is not supported, don't add it to the new metadata object. + if ( ! transformSupportedProps.includes( prop ) ) { + return obj; + } + obj[ prop ] = + prop === 'bindings' ? bindingsCallback( value ) : value; return obj; - } - - if ( prop === 'bindings' ) { - // Adapt bindings object based on the mapping. - obj[ prop ] = Object.entries( value ).reduce( - ( transformedObj, [ originalKey, originalObj ] ) => { - const transformedKey = - BINDINGS_ATTRIBUTES_MAPPING[ fromBlockName ][ - originalKey - ][ toBlockName ]; - - transformedObj[ transformedKey ] = originalObj; - return transformedObj; - }, - {} - ); - } else { - obj[ prop ] = value; - } + }, + {} + ); - return obj; - }, {} ); + // Return undefined if object is empty. + return Object.keys( newMetadata ).length ? newMetadata : undefined; }