From 4061516548b51c7cb74cdbdbd74b3bcc122787a5 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 14 Nov 2022 14:23:57 -0700 Subject: [PATCH 01/76] Performance Tests: Only run 1 round of tests during PR commits In the performance tests CI workflow we have been running every test suite three times for each branch under test. The goal of this work, introduced in #33710, was to reduce variation in the reported data from the tests. Unfortunately after measuring the data produced by our test runs, and by running experiments that run the test suites thirty times over, the overall variation is explained primarily by noise in the Github Actions container running our jobs. If running the test suites three times each reduces the variation in the results then it's not detectable above the amount of variation introduced beyond our control. Because these additional rounds extend the perf-test runtime by around twenty minutes on each PR we're reducing the number of rounds to a single pass for PR commits. This will free up compute resources and remove the performance tests as a bottleneck in the PR workflow. Additional work can and should be done to further remove variance in the testing results, but this practical step will remove an obstacle from developer iteration speed without reducing the quality of the numbers being reported. --- .github/workflows/performance.yml | 2 +- bin/plugin/cli.js | 4 ++++ bin/plugin/commands/performance.js | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index e878724c1b5f19..675642df3c26c9 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -61,7 +61,7 @@ jobs: WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' readme.txt) IFS=. read -ra WP_VERSION_ARRAY <<< "$WP_VERSION" WP_MAJOR="${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}" - ./bin/plugin/cli.js perf $GITHUB_SHA debd225d007f4e441ceec80fbd6fa96653f94737 --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" + ./bin/plugin/cli.js perf $GITHUB_SHA debd225d007f4e441ceec80fbd6fa96653f94737 --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" - uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 # v6.3.3 if: github.event_name == 'push' diff --git a/bin/plugin/cli.js b/bin/plugin/cli.js index ed2fef4a5b05f4..0101c9169171ee 100755 --- a/bin/plugin/cli.js +++ b/bin/plugin/cli.js @@ -94,6 +94,10 @@ program .command( 'performance-tests [branches...]' ) .alias( 'perf' ) .option( ...ciOption ) + .option( + '--rounds ', + 'Run each test suite this many times for each branch; results are summarized, default = 1' + ) .option( '--tests-branch ', "Use this branch's performance test files" diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index 2aa8a2c9a7f5c6..7101dc65118089 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -22,6 +22,7 @@ const config = require( '../config' ); * @typedef WPPerformanceCommandOptions * * @property {boolean=} ci Run on CI. + * @property {number=} rounds Run each test suite this many times for each branch. * @property {string=} testsBranch The branch whose performance test files will be used for testing. * @property {string=} wpVersion The WordPress version to be used as the base install for testing. */ @@ -176,6 +177,7 @@ async function runTestSuite( testSuite, performanceTestDirectory ) { */ async function runPerformanceTests( branches, options ) { const runningInCI = !! process.env.CI || !! options.ci; + const TEST_ROUNDS = options.rounds || 1; // The default value doesn't work because commander provides an array. if ( branches.length === 0 ) { @@ -330,7 +332,7 @@ async function runPerformanceTests( branches, options ) { /** @type {Array>} */ const rawResults = []; // Alternate three times between branches. - for ( let i = 0; i < 3; i++ ) { + for ( let i = 0; i < TEST_ROUNDS; i++ ) { rawResults[ i ] = {}; for ( const branch of branches ) { // @ts-ignore From da582f37719c4c47e0d567f5a9651bdeaf128ef1 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 16 Nov 2022 12:57:59 -0800 Subject: [PATCH 02/76] Do not unnecessarily pass post ID to various query loop aware functions. --- packages/block-library/src/post-featured-image/index.php | 6 +++--- packages/block-library/src/post-title/index.php | 9 ++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/block-library/src/post-featured-image/index.php b/packages/block-library/src/post-featured-image/index.php index 068b7eeac6899f..352ecaa15afa2c 100644 --- a/packages/block-library/src/post-featured-image/index.php +++ b/packages/block-library/src/post-featured-image/index.php @@ -27,7 +27,7 @@ function render_block_core_post_featured_image( $attributes, $content, $block ) $is_link = isset( $attributes['isLink'] ) && $attributes['isLink']; $size_slug = isset( $attributes['sizeSlug'] ) ? $attributes['sizeSlug'] : 'post-thumbnail'; - $post_title = trim( strip_tags( get_the_title( $post_ID ) ) ); + $post_title = trim( strip_tags( get_the_title() ) ); $attr = get_block_core_post_featured_image_border_attributes( $attributes ); $overlay_markup = get_block_core_post_featured_image_overlay_element_markup( $attributes ); @@ -43,7 +43,7 @@ function render_block_core_post_featured_image( $attributes, $content, $block ) $attr['style'] = empty( $attr['style'] ) ? $extra_styles : $attr['style'] . $extra_styles; } - $featured_image = get_the_post_thumbnail( $post_ID, $size_slug, $attr ); + $featured_image = get_the_post_thumbnail( null, $size_slug, $attr ); if ( ! $featured_image ) { return ''; } @@ -52,7 +52,7 @@ function render_block_core_post_featured_image( $attributes, $content, $block ) $rel = ! empty( $attributes['rel'] ) ? 'rel="' . esc_attr( $attributes['rel'] ) . '"' : ''; $featured_image = sprintf( '%4$s%5$s', - get_the_permalink( $post_ID ), + get_the_permalink(), esc_attr( $link_target ), $rel, $featured_image, diff --git a/packages/block-library/src/post-title/index.php b/packages/block-library/src/post-title/index.php index 125a2d81ddcbe3..3a90e46763d795 100644 --- a/packages/block-library/src/post-title/index.php +++ b/packages/block-library/src/post-title/index.php @@ -15,12 +15,7 @@ * @return string Returns the filtered post title for the current post wrapped inside "h1" tags. */ function render_block_core_post_title( $attributes, $content, $block ) { - if ( ! isset( $block->context['postId'] ) ) { - return ''; - } - - $post_ID = $block->context['postId']; - $title = get_the_title(); + $title = get_the_title(); if ( ! $title ) { return ''; @@ -35,7 +30,7 @@ function render_block_core_post_title( $attributes, $content, $block ) { if ( isset( $attributes['isLink'] ) && $attributes['isLink'] ) { $rel = ! empty( $attributes['rel'] ) ? 'rel="' . esc_attr( $attributes['rel'] ) . '"' : ''; - $title = sprintf( '%4$s', get_the_permalink( $post_ID ), esc_attr( $attributes['linkTarget'] ), $rel, $title ); + $title = sprintf( '%4$s', get_the_permalink(), esc_attr( $attributes['linkTarget'] ), $rel, $title ); } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $align_class_name ) ); From 6696fdd5b8f0f8bf31b0c646d1752f96b9f0e02d Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 16 Nov 2022 13:02:14 -0800 Subject: [PATCH 03/76] Remove unnecessary lookup of postId context. --- packages/block-library/src/post-featured-image/index.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/block-library/src/post-featured-image/index.php b/packages/block-library/src/post-featured-image/index.php index 352ecaa15afa2c..b4c5cc33c53fee 100644 --- a/packages/block-library/src/post-featured-image/index.php +++ b/packages/block-library/src/post-featured-image/index.php @@ -14,11 +14,6 @@ * @return string Returns the featured image for the current post. */ function render_block_core_post_featured_image( $attributes, $content, $block ) { - if ( ! isset( $block->context['postId'] ) ) { - return ''; - } - $post_ID = $block->context['postId']; - // Check is needed for backward compatibility with third-party plugins // that might rely on the `in_the_loop` check; calling `the_post` sets it to true. if ( ! in_the_loop() && have_posts() ) { From 83a5df739dc904bc106a36002674aa7c68130aee Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 16 Nov 2022 22:24:27 +0000 Subject: [PATCH 04/76] Update Changelog for 14.5.2 --- changelog.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/changelog.txt b/changelog.txt index 6beec6ef1b1194..548a18a5ef4642 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,22 @@ == Changelog == += 14.5.2 = + + + +## Changelog + +### Bug Fixes + +- Tag Processor: Prevent bugs from pre-PHP8 strspn/strcspn behavior. ([45822](https://github.com/WordPress/gutenberg/pull/45822)) + +## Contributors + +The following contributors merged PRs in this release: + +@dmsnell + + = 14.6.0-rc.1 = ## Changelog From dbb9487255f2808eb5543cbc2dfa26b8757d3d04 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 14 Nov 2022 17:32:41 -0700 Subject: [PATCH 05/76] Tag Processor: Merge independent tests into single file --- .../WP_HTML_Tag_Processor_Isolated_Test.php | 145 -- phpunit/html/WP_HTML_Tag_Processor_Test.php | 1187 ----------------- ...est.php => wp-html-tag-processor-test.php} | 23 +- .../html/wp-html-tag-processor-wp-test.php | 91 -- 4 files changed, 2 insertions(+), 1444 deletions(-) delete mode 100644 phpunit/html/WP_HTML_Tag_Processor_Isolated_Test.php delete mode 100644 phpunit/html/WP_HTML_Tag_Processor_Test.php rename phpunit/html/{wp-html-tag-processor-standalone-test.php => wp-html-tag-processor-test.php} (98%) delete mode 100644 phpunit/html/wp-html-tag-processor-wp-test.php diff --git a/phpunit/html/WP_HTML_Tag_Processor_Isolated_Test.php b/phpunit/html/WP_HTML_Tag_Processor_Isolated_Test.php deleted file mode 100644 index d593c5d2f4fe92..00000000000000 --- a/phpunit/html/WP_HTML_Tag_Processor_Isolated_Test.php +++ /dev/null @@ -1,145 +0,0 @@ -' ); - - $this->expectException( Exception::class ); - - $p->next_tag(); - $p->set_attribute( $attribute_name, 'test' ); - - $this->assertEquals( '', (string) $p ); - } - - /** - * Attribute names with invalid characters should be rejected. - * - * When WP_DEBUG isn't set we want to quietly fail to set the - * invalid attribute to avoid breaking the HTML and to do so - * without breaking the entire page. - * - * @dataProvider data_invalid_attribute_names - * @covers set_attribute - */ - public function test_set_attribute_silently_fails_when_given_invalid_attribute_names_outside_of_debug_mode( $attribute_name ) { - $p = new WP_HTML_Tag_Processor( '' ); - - $p->next_tag(); - $p->set_attribute( $attribute_name, 'test' ); - - $this->assertEquals( '', (string) $p ); - } - - /** - * Data provider with invalid HTML attribute names. - * - * @return array { - * @type string $attribute_name Text considered invalid for HTML attribute names. - * } - */ - public function data_invalid_attribute_names() { - return array( - 'controls_null' => array( "i\x00d" ), - 'controls_newline' => array( "\nbroken-expectations" ), - 'space' => array( 'aria label' ), - 'double-quote' => array( '"id"' ), - 'single-quote' => array( "'id'" ), - 'greater-than' => array( 'sneaky>script' ), - 'solidus' => array( 'data/test-id' ), - 'equals' => array( 'checked=checked' ), - 'noncharacters_1' => array( html_entity_decode( 'anything﷐' ) ), - 'noncharacters_2' => array( html_entity_decode( 'te￿st' ) ), - 'noncharacters_3' => array( html_entity_decode( 'te𯿾st' ) ), - 'noncharacters_4' => array( html_entity_decode( 'te󟿿st' ) ), - 'noncharacters_5' => array( html_entity_decode( '􏿾' ) ), - 'wp_no_lt' => array( 'id array( 'class<script' ), - ); - } - - /** - * Attribute names with only valid characters should not be rejected. - * - * > Attributes have a name and a value. Attribute names must - * > consist of one or more characters other than controls, - * > U+0020 SPACE, U+0022 ("), U+0027 ('), U+003E (>), - * > U+002F (/), U+003D (=), and noncharacters. - * - * @see https://html.spec.whatwg.org/#attributes-2 - * - * @dataProvider data_valid_attribute_names - * @covers set_attribute - */ - public function test_set_attribute_does_not_reject_valid_attribute_names( $attribute_name ) { - define( 'WP_DEBUG', true ); - $p = new WP_HTML_Tag_Processor( '' ); - - $p->next_tag(); - $p->set_attribute( $attribute_name, 'test' ); - - $this->assertEquals( "", (string) $p ); - } - - /** - * Data provider with valid HTML attribute names. - * - * @return array { - * @type string $attribute_name Text considered valid for HTML attribute names. - * } - */ - public function data_valid_attribute_names() { - return array( - 'ascii_letters' => array( 'abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ' ), - 'ascii_numbers' => array( '0123456789' ), - 'symbols' => array( '!@#$%^*()[]{};:\\||,.?`~£§±' ), - 'emoji' => array( '❌' ), - 'utf8_diacritics' => array( 'ÁÄÂÀÃÅČÇĆĎÉĚËÈÊẼĔȆĞÍÌÎÏİŇÑÓÖÒÔÕØŘŔŠŞŤÚŮÜÙÛÝŸŽáäâàãåčçćďéěëèêẽĕȇğíìîïıňñóöòôõøðřŕšşťúůüùûýÿžþÞĐđßÆa' ), - 'hebrew_accents' => array( html_entity_decode( '֝a' ) ), - // See https://arxiv.org/abs/2111.00169. - 'rtl_magic' => array( html_entity_decode( '⁧⁦abc⁩⁦def⁩⁩' ) ), - // Only a single unicode "noncharacter" should be rejected. Specific byte segments used in the "noncharacter" sequence are valid. - 'noncharacter_segments' => array( "\xFF\xFE" ), - ); - } - -} diff --git a/phpunit/html/WP_HTML_Tag_Processor_Test.php b/phpunit/html/WP_HTML_Tag_Processor_Test.php deleted file mode 100644 index cec0fa555300a0..00000000000000 --- a/phpunit/html/WP_HTML_Tag_Processor_Test.php +++ /dev/null @@ -1,1187 +0,0 @@ -Text'; - const HTML_WITH_CLASSES = '
Text
'; - const HTML_MALFORMED = '
Back to notifications
'; - - /** - * @ticket 56299 - * - * @covers get_tag - */ - public function test_get_tag_returns_null_before_finding_tags() { - $p = new WP_HTML_Tag_Processor( '
Test
' ); - $this->assertNull( $p->get_tag() ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - * @covers get_tag - */ - public function test_get_tag_returns_null_when_not_in_open_tag() { - $p = new WP_HTML_Tag_Processor( '
Test
' ); - $this->assertFalse( $p->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); - $this->assertNull( $p->get_tag(), 'Accessing a non-existing tag did not return null' ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - * @covers get_tag - */ - public function test_get_tag_returns_open_tag_name() { - $p = new WP_HTML_Tag_Processor( '
Test
' ); - $this->assertTrue( $p->next_tag( 'div' ), 'Querying an existing tag did not return true' ); - $this->assertSame( 'DIV', $p->get_tag(), 'Accessing an existing tag name did not return "div"' ); - } - - /** - * @ticket 56299 - * - * @covers get_attribute - */ - public function test_get_attribute_returns_null_before_finding_tags() { - $p = new WP_HTML_Tag_Processor( '
Test
' ); - $this->assertNull( $p->get_attribute( 'class' ) ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - * @covers get_attribute - */ - public function test_get_attribute_returns_null_when_not_in_open_tag() { - $p = new WP_HTML_Tag_Processor( '
Test
' ); - $this->assertFalse( $p->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); - $this->assertNull( $p->get_attribute( 'class' ), 'Accessing an attribute of a non-existing tag did not return null' ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - * @covers get_attribute - */ - public function test_get_attribute_returns_null_when_attribute_missing() { - $p = new WP_HTML_Tag_Processor( '
Test
' ); - $this->assertTrue( $p->next_tag( 'div' ), 'Querying an existing tag did not return true' ); - $this->assertNull( $p->get_attribute( 'test-id' ), 'Accessing a non-existing attribute did not return null' ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - * @covers get_attribute - */ - public function test_get_attribute_returns_attribute_value() { - $p = new WP_HTML_Tag_Processor( '
Test
' ); - $this->assertTrue( $p->next_tag( 'div' ), 'Querying an existing tag did not return true' ); - $this->assertSame( 'test', $p->get_attribute( 'class' ), 'Accessing a class="test" attribute value did not return "test"' ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - * @covers get_attribute - */ - public function test_get_attribute_returns_true_for_boolean_attribute() { - $p = new WP_HTML_Tag_Processor( '
Test
' ); - $this->assertTrue( $p->next_tag( array( 'class_name' => 'test' ) ), 'Querying an existing tag did not return true' ); - $this->assertTrue( $p->get_attribute( 'enabled' ), 'Accessing a boolean "enabled" attribute value did not return true' ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - * @covers get_attribute - */ - public function test_get_attribute_returns_string_for_truthy_attributes() { - $p = new WP_HTML_Tag_Processor( '' ); - $this->assertTrue( $p->next_tag( array() ), 'Querying an existing tag did not return true' ); - $this->assertSame( 'enabled', $p->get_attribute( 'enabled' ), 'Accessing a boolean "enabled" attribute value did not return true' ); - $this->assertSame( '1', $p->get_attribute( 'checked' ), 'Accessing a checked=1 attribute value did not return "1"' ); - $this->assertSame( 'true', $p->get_attribute( 'hidden' ), 'Accessing a hidden="true" attribute value did not return "true"' ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - * @covers get_attribute - */ - public function test_attributes_parser_treats_slash_as_attribute_separator() { - $p = new WP_HTML_Tag_Processor( '
Test
' ); - $this->assertTrue( $p->next_tag( array() ), 'Querying an existing tag did not return true' ); - $this->assertTrue( $p->get_attribute( 'a' ), 'Accessing an existing attribute did not return true' ); - $this->assertTrue( $p->get_attribute( 'b' ), 'Accessing an existing attribute did not return true' ); - $this->assertTrue( $p->get_attribute( 'c' ), 'Accessing an existing attribute did not return true' ); - $this->assertTrue( $p->get_attribute( 'd' ), 'Accessing an existing attribute did not return true' ); - $this->assertSame( 'test', $p->get_attribute( 'e' ), 'Accessing an existing e="test" did not return "test"' ); - } - - /** - * @ticket 56299 - * - * @covers __toString - */ - public function test_tostring_applies_the_updates_so_far_and_keeps_the_processor_on_the_current_tag() { - $p = new WP_HTML_Tag_Processor( '
Test
' ); - $p->next_tag(); - $p->remove_attribute( 'id' ); - - $p->next_tag(); - $p->set_attribute( 'id', 'div-id-1' ); - $p->add_class( 'new_class_1' ); - $this->assertSame( - '
Test
', - (string) $p, - 'Calling __toString after updating the attributes of the second tag returned different HTML than expected' - ); - - $p->set_attribute( 'id', 'div-id-2' ); - $p->add_class( 'new_class_2' ); - $this->assertSame( - '
Test
', - (string) $p, - 'Calling __toString after updating the attributes of the second tag for the second time returned different HTML than expected' - ); - - $p->next_tag(); - $p->remove_attribute( 'id' ); - $this->assertSame( - '
Test
', - (string) $p, - 'Calling __toString after removing the id attribute of the third tag returned different HTML than expected' - ); - - } - - /** - * @ticket 56299 - * - * @covers __toString - */ - public function test_tostring_without_updating_any_attributes_returns_the_original_html() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $this->assertSame( self::HTML_SIMPLE, (string) $p ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - */ - public function test_next_tag_with_no_arguments_should_find_the_next_existing_tag() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $this->assertTrue( $p->next_tag(), 'Querying an existing tag did not return true' ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - */ - public function test_next_tag_should_return_false_for_a_non_existing_tag() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $this->assertFalse( $p->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - * @covers __toString - */ - public function test_set_attribute_on_a_non_existing_tag_does_not_change_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $this->assertFalse( $p->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); - $this->assertFalse( $p->next_tag( 'div' ), 'Querying a non-existing tag did not return false' ); - $p->set_attribute( 'id', 'primary' ); - $this->assertSame( - self::HTML_SIMPLE, - (string) $p, - 'Calling __toString after updating a non-existing tag returned an HTML that was different from the original HTML' - ); - } - - /** - * @ticket 56299 - * - * @covers set_attribute - * @covers __toString - */ - public function test_set_attribute_with_a_non_existing_attribute_adds_a_new_attribute_to_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->set_attribute( 'test-attribute', 'test-value' ); - $this->assertSame( '
Text
', (string) $p ); - } - - /** - * According to HTML spec, only the first instance of an attribute counts. - * The other ones are ignored. - * - * @ticket 56299 - * - * @covers set_attribute - * @covers __toString - */ - public function test_update_first_when_duplicated_attribute() { - $p = new WP_HTML_Tag_Processor( '
Text
' ); - $p->next_tag(); - $p->set_attribute( 'id', 'updated-id' ); - $this->assertSame( '
Text
', (string) $p ); - } - - /** - * @ticket 56299 - * - * @covers set_attribute - * @covers __toString - */ - public function test_set_attribute_with_an_existing_attribute_name_updates_its_value_in_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->set_attribute( 'id', 'new-id' ); - $this->assertSame( '
Text
', (string) $p ); - } - - /** - * @ticket 56299 - * - * @covers set_attribute - * @covers __toString - */ - public function test_next_tag_and_set_attribute_in_a_loop_update_all_tags_in_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - while ( $p->next_tag() ) { - $p->set_attribute( 'data-foo', 'bar' ); - } - - $this->assertSame( '
Text
', (string) $p ); - } - - /** - * Removing an attribute that's listed many times, e.g. `
` should remove - * all its instances and output just `
`. - * - * Today, however, WP_HTML_Tag_Processor only removes the first such attribute. It seems like a corner case - * and introducing additional complexity to correctly handle this scenario doesn't seem to be worth it. - * Let's revisit if and when this becomes a problem. - * - * This test is in place to confirm this behavior, while incorrect, is well-defined. - * - * @ticket 56299 - * - * @covers remove_attribute - * @covers __toString - */ - public function test_remove_first_when_duplicated_attribute() { - $p = new WP_HTML_Tag_Processor( '
Text
' ); - $p->next_tag(); - $p->remove_attribute( 'id' ); - $this->assertSame( '
Text
', (string) $p ); - } - - /** - * @ticket 56299 - * - * @covers remove_attribute - * @covers __toString - */ - public function test_remove_attribute_with_an_existing_attribute_name_removes_it_from_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->remove_attribute( 'id' ); - $this->assertSame( '
Text
', (string) $p ); - } - - /** - * @ticket 56299 - * - * @covers remove_attribute - * @covers __toString - */ - public function test_remove_attribute_with_a_non_existing_attribute_name_does_not_change_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->remove_attribute( 'no-such-attribute' ); - $this->assertSame( self::HTML_SIMPLE, (string) $p ); - } - - /** - * @ticket 56299 - * - * @covers add_class - * @covers __toString - */ - public function test_add_class_creates_a_class_attribute_when_there_is_none() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->add_class( 'foo-class' ); - $this->assertSame( '
Text
', (string) $p ); - } - - /** - * @ticket 56299 - * - * @covers add_class - * @covers __toString - */ - public function test_calling_add_class_twice_creates_a_class_attribute_with_both_class_names_when_there_is_no_class_attribute() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->add_class( 'foo-class' ); - $p->add_class( 'bar-class' ); - $this->assertSame( '
Text
', (string) $p ); - } - - /** - * @ticket 56299 - * - * @covers remove_class - * @covers __toString - */ - public function test_remove_class_does_not_change_the_markup_when_there_is_no_class_attribute() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->remove_class( 'foo-class' ); - $this->assertSame( self::HTML_SIMPLE, (string) $p ); - } - - /** - * @ticket 56299 - * - * @covers add_class - * @covers __toString - */ - public function test_add_class_appends_class_names_to_the_existing_class_attribute_when_one_already_exists() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->add_class( 'foo-class' ); - $p->add_class( 'bar-class' ); - $this->assertSame( - '
Text
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers remove_class - * @covers __toString - */ - public function test_remove_class_removes_a_single_class_from_the_class_attribute_when_one_exists() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->remove_class( 'main' ); - $this->assertSame( - '
Text
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers remove_class - * @covers __toString - */ - public function test_calling_remove_class_with_all_listed_class_names_removes_the_existing_class_attribute_from_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->remove_class( 'main' ); - $p->remove_class( 'with-border' ); - $this->assertSame( - '
Text
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers add_class - * @covers __toString - */ - public function test_add_class_does_not_add_duplicate_class_names() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->add_class( 'with-border' ); - $this->assertSame( - '
Text
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers add_class - * @covers __toString - */ - public function test_add_class_preserves_class_name_order_when_a_duplicate_class_name_is_added() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->add_class( 'main' ); - $this->assertSame( - '
Text
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers add_class - * @covers __toString - */ - public function test_add_class_when_there_is_a_class_attribute_with_excessive_whitespaces() { - $p = new WP_HTML_Tag_Processor( - '
Text
' - ); - $p->next_tag(); - $p->add_class( 'foo-class' ); - $this->assertSame( - '
Text
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers remove_class - * @covers __toString - */ - public function test_remove_class_preserves_whitespaces_when_there_is_a_class_attribute_with_excessive_whitespaces() { - $p = new WP_HTML_Tag_Processor( - '
Text
' - ); - $p->next_tag(); - $p->remove_class( 'with-border' ); - $this->assertSame( - '
Text
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers remove_class - * @covers __toString - */ - public function test_removing_all_classes_removes_the_existing_class_attribute_from_the_markup_even_when_excessive_whitespaces_are_present() { - $p = new WP_HTML_Tag_Processor( - '
Text
' - ); - $p->next_tag(); - $p->remove_class( 'main' ); - $p->remove_class( 'with-border' ); - $this->assertSame( - '
Text
', - (string) $p - ); - } - - /** - * When both set_attribute('class', $value) and add_class( $different_value ) are called, - * the final class name should be $value. In other words, the `add_class` call should be ignored, - * and the `set_attribute` call should win. This holds regardless of the order in which these methods - * are called. - * - * @ticket 56299 - * - * @covers add_class - * @covers set_attribute - * @covers __toString - */ - public function test_set_attribute_takes_priority_over_add_class() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->add_class( 'add_class' ); - $p->set_attribute( 'class', 'set_attribute' ); - $this->assertSame( - '
Text
', - (string) $p, - 'Calling __toString after updating first tag\'s attributes did not return the expected HTML' - ); - - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->set_attribute( 'class', 'set_attribute' ); - $p->add_class( 'add_class' ); - $this->assertSame( - '
Text
', - (string) $p, - 'Calling __toString after updating second tag\'s attributes did not return the expected HTML' - ); - } - - /** - * @ticket 56299 - * - * @covers set_attribute - * @covers remove_attribute - * @covers add_class - * @covers remove_class - * @covers __toString - */ - public function test_advanced_use_case() { - $input = << -
-
-
- - - - - - - -
-
-
-HTML; - - $expected_output = << -
-
-
- - - - - - - -
-
-
-HTML; - - $p = new WP_HTML_Tag_Processor( $input ); - $this->assertTrue( $p->next_tag( 'div' ), 'Querying an existing tag did not return true' ); - $p->set_attribute( 'data-details', '{ "key": "value" }' ); - $p->add_class( 'is-processed' ); - $this->assertTrue( - $p->next_tag( - array( - 'tag_name' => 'div', - 'class_name' => 'BtnGroup', - ) - ), - 'Querying an existing tag did not return true' - ); - $p->remove_class( 'BtnGroup' ); - $p->add_class( 'button-group' ); - $p->add_class( 'Another-Mixed-Case' ); - $this->assertTrue( - $p->next_tag( - array( - 'tag_name' => 'div', - 'class_name' => 'BtnGroup', - ) - ), - 'Querying an existing tag did not return true' - ); - $p->remove_class( 'BtnGroup' ); - $p->add_class( 'button-group' ); - $p->add_class( 'Another-Mixed-Case' ); - $this->assertTrue( - $p->next_tag( - array( - 'tag_name' => 'button', - 'class_name' => 'btn', - 'match_offset' => 3, - ) - ), - 'Querying an existing tag did not return true' - ); - $p->remove_attribute( 'class' ); - $this->assertFalse( $p->next_tag( 'non-existent' ), 'Querying a non-existing tag did not return false' ); - $p->set_attribute( 'class', 'test' ); - $this->assertSame( $expected_output, (string) $p, 'Calling __toString after updating the attributes did not return the expected HTML' ); - } - - /** - * @ticket 56299 - * - * @covers remove_attribute - * @covers set_attribute - * @covers __toString - */ - public function test_correctly_parses_html_attributes_wrapped_in_single_quotation_marks() { - $p = new WP_HTML_Tag_Processor( - '
Text
' - ); - $p->next_tag( - array( - 'tag_name' => 'div', - 'id' => 'first', - ) - ); - $p->remove_attribute( 'id' ); - $p->next_tag( - array( - 'tag_name' => 'span', - 'id' => 'second', - ) - ); - $p->set_attribute( 'id', 'single-quote' ); - $this->assertSame( - '
Text
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers set_attribute - * @covers __toString - */ - public function test_set_attribute_with_value_equals_to_true_adds_a_boolean_html_attribute_with_implicit_value() { - $p = new WP_HTML_Tag_Processor( - '
' - ); - $p->next_tag( 'input' ); - $p->set_attribute( 'checked', true ); - $this->assertSame( - '
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers set_attribute - * @covers __toString - */ - public function test_setting_a_boolean_attribute_to_false_removes_it_from_the_markup() { - $p = new WP_HTML_Tag_Processor( - '
' - ); - $p->next_tag( 'input' ); - $p->set_attribute( 'checked', false ); - $this->assertSame( - '
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers set_attribute - * @covers __toString - */ - public function test_setting_a_missing_attribute_to_false_does_not_change_the_markup() { - $html_input = '
'; - $p = new WP_HTML_Tag_Processor( $html_input ); - $p->next_tag( 'input' ); - $p->set_attribute( 'checked', false ); - $this->assertSame( $html_input, (string) $p ); - } - - /** - * @ticket 56299 - * - * @covers set_attribute - * @covers __toString - */ - public function test_setting_a_boolean_attribute_to_a_string_value_adds_explicit_value_to_the_markup() { - $p = new WP_HTML_Tag_Processor( - '
' - ); - $p->next_tag( 'input' ); - $p->set_attribute( 'checked', 'checked' ); - $this->assertSame( - '
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers get_tag - * @covers next_tag - */ - public function test_unclosed_script_tag_should_not_cause_an_infinite_loop() { - $p = new WP_HTML_Tag_Processor( '
', - ); - - $examples['Simple uppercase script tag'] = array( - '
', - ); - - $examples['Script with a comment opener inside should end at the next script tag closer (dash dash escaped state)'] = array( - '
-->', - ); - - $examples['Script with a comment opener and a script tag opener inside should end two script tag closer later (double escaped state)'] = array( - '
-->', - ); - - $examples['Double escaped script with a tricky opener'] = array( - '">
', - ); - - $examples['Double escaped script with a tricky closer'] = array( - '">
', - ); - - $examples['Double escaped, then escaped, then double escaped'] = array( - '
', - ); - - $examples['Script with a commented a script tag opener inside should at the next tag closer (dash dash escaped state)'] = array( - '
-->', - ); - - $examples['Script closer with another script tag in closer attributes'] = array( - '
', - ); - - $examples['Script closer with attributes'] = array( - '
', - ); - - $examples['Script opener with title closer inside'] = array( - '
', - ); - - $examples['Complex script with many parsing states'] = array( - '-->
-->', - ); - return $examples; - } - - /** - * @ticket 56299 - * - * @covers next_tag - * - * @dataProvider data_rcdata_state - */ - public function test_next_tag_ignores_the_contents_of_a_rcdata_tag( $rcdata_then_div, $rcdata_tag ) { - $p = new WP_HTML_Tag_Processor( $rcdata_then_div ); - $p->next_tag(); - $this->assertSame( strtoupper( $rcdata_tag ), $p->get_tag(), "The first found tag was not '$rcdata_tag'" ); - $p->next_tag(); - $this->assertSame( 'DIV', $p->get_tag(), "The second found tag was not 'div'" ); - } - - /** - * Data provider for test_ignores_contents_of_a_rcdata_tag(). - * - * @return array { - * @type array { - * @type string $rcdata_then_div The HTML snippet containing RCDATA and div tags. - * @type string $rcdata_tag The RCDATA tag. - * } - * } - */ - public function data_rcdata_state() { - $examples = array(); - $examples['Simple textarea'] = array( - '
', - 'textarea', - ); - - $examples['Simple title'] = array( - '<span class="d-none d-md-inline">Back to notifications</title</span>
', - 'title', - ); - - $examples['Comment opener inside a textarea tag should be ignored'] = array( - '
-->', - 'textarea', - ); - - $examples['Textarea closer with another textarea tag in closer attributes'] = array( - '
', - 'textarea', - ); - - $examples['Textarea closer with attributes'] = array( - '
', - 'textarea', - ); - - $examples['Textarea opener with title closer inside'] = array( - '
', - 'textarea', - ); - return $examples; - } - - /** - * @ticket 56299 - * - * @covers next_tag - * @covers set_attribute - * @covers __toString - */ - public function test_can_query_and_update_wrongly_nested_tags() { - $p = new WP_HTML_Tag_Processor( - '123

456789

' - ); - $p->next_tag( 'span' ); - $p->set_attribute( 'class', 'span-class' ); - $p->next_tag( 'p' ); - $p->set_attribute( 'class', 'p-class' ); - $this->assertSame( - '123

456789

', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - * @covers remove_attribute - * @covers __toString - */ - public function test_removing_attributes_works_even_in_malformed_html() { - $p = new WP_HTML_Tag_Processor( self::HTML_MALFORMED ); - $p->next_tag( 'span' ); - $p->remove_attribute( 'Notifications<' ); - $this->assertSame( - '
Back to notifications
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers next_Tag - * @covers set_attribute - * @covers __toString - */ - public function test_updating_attributes_works_even_in_malformed_html_1() { - $p = new WP_HTML_Tag_Processor( self::HTML_MALFORMED ); - $p->next_tag( 'span' ); - $p->set_attribute( 'id', 'first' ); - $p->next_tag( 'span' ); - $p->set_attribute( 'id', 'second' ); - $this->assertSame( - '
Back to notifications
', - (string) $p - ); - } - - /** - * @ticket 56299 - * - * @covers next_tag - * @covers set_attribute - * @covers add_class - * @covers __toString - * - * @dataProvider data_malformed_tag - */ - public function test_updating_attributes_works_even_in_malformed_html_2( $html_input, $html_expected ) { - $p = new WP_HTML_Tag_Processor( $html_input ); - $p->next_tag(); - $p->set_attribute( 'foo', 'bar' ); - $p->add_class( 'firstTag' ); - $p->next_tag(); - $p->add_class( 'secondTag' ); - $this->assertSame( - $html_expected, - (string) $p - ); - } - - /** - * Data provider for test_updates_when_malformed_tag(). - * - * @return array { - * @type array { - * @type string $html_input The input HTML snippet. - * @type string $html_expected The expected HTML snippet after processing. - * } - * } - */ - public function data_malformed_tag() { - $null_byte = chr( 0 ); - $examples = array(); - $examples['Invalid entity inside attribute value'] = array( - 'test', - 'test', - ); - - $examples['HTML tag opening inside attribute value'] = array( - '
This <is> a <strong is="true">thing.
test', - '
This <is> a <strong is="true">thing.
test', - ); - - $examples['HTML tag brackets in attribute values and data markup'] = array( - '
This <is> a <strong is="true">thing.
test', - '
This <is> a <strong is="true">thing.
test', - ); - - $examples['Single and double quotes in attribute value'] = array( - '

test', - '

test', - ); - - $examples['Unquoted attribute values'] = array( - '


test', - '
test', - ); - - $examples['Double-quotes escaped in double-quote attribute value'] = array( - '
test', - '
test', - ); - - $examples['Unquoted attribute value'] = array( - '
test', - '
test', - ); - - $examples['Unquoted attribute value with tag-like value'] = array( - '
>test', - '
>test', - ); - - $examples['Unquoted attribute value with tag-like value followed by tag-like data'] = array( - '
>test', - '
>test', - ); - - $examples['1'] = array( - '
test', - '
test', - ); - - $examples['2'] = array( - '
test', - '
test', - ); - - $examples['4'] = array( - '
test', - '
test', - ); - - $examples['5'] = array( - '
code>test', - '
code>test', - ); - - $examples['6'] = array( - '
test', - '
test', - ); - - $examples['7'] = array( - '
test', - '
test', - ); - - $examples['8'] = array( - '
id="test">test', - '
id="test">test', - ); - - $examples['9'] = array( - '
test', - '
test', - ); - - $examples['10'] = array( - 'test', - 'test', - ); - - $examples['11'] = array( - 'The applicative operator <* works well in Haskell; is what?test', - 'The applicative operator <* works well in Haskell; is what?test', - ); - - $examples['12'] = array( - '<3 is a heart but is a tag.test', - '<3 is a heart but is a tag.test', - ); - - $examples['13'] = array( - 'test', - 'test', - ); - - $examples['14'] = array( - 'test', - 'test', - ); - - $examples['15'] = array( - ' a HTML Tag]]>test', - ' a HTML Tag]]>test', - ); - - $examples['16'] = array( - '
test', - '
test', - ); - - $examples['17'] = array( - '
test', - '
test', - ); - - $examples['18'] = array( - '
test', - '
test', - ); - - $examples['19'] = array( - '
test', - '
test', - ); - - $examples['20'] = array( - '
test', - '
test', - ); - - $examples['21'] = array( - '
test', - '
test', - ); - - $examples['22'] = array( - '
test', - '
test', - ); - - $examples['23'] = array( - '
test', - '
test', - ); - - $examples['24'] = array( - '
test', - '
test', - ); - - $examples['25'] = array( - '
test', - '
test', - ); - - $examples['Multiple unclosed tags treated as a single tag'] = array( - '
-test', - '
-test', - ); - - $examples['27'] = array( - '
test', - '
test', - ); - - $examples['28'] = array( - '
test', - '
test', - ); - - return $examples; - } -} diff --git a/phpunit/html/wp-html-tag-processor-standalone-test.php b/phpunit/html/wp-html-tag-processor-test.php similarity index 98% rename from phpunit/html/wp-html-tag-processor-standalone-test.php rename to phpunit/html/wp-html-tag-processor-test.php index 8079db28f52be7..e66b1d50758d0b 100644 --- a/phpunit/html/wp-html-tag-processor-standalone-test.php +++ b/phpunit/html/wp-html-tag-processor-test.php @@ -1,30 +1,11 @@ Text
'; const HTML_WITH_CLASSES = '
Text
'; const HTML_MALFORMED = '
Back to notifications
'; @@ -951,7 +932,7 @@ public function data_script_state() { public function test_next_tag_ignores_the_contents_of_a_rcdata_tag( $rcdata_then_div, $rcdata_tag ) { $p = new WP_HTML_Tag_Processor( $rcdata_then_div ); $p->next_tag(); - $this->assertSame( $rcdata_tag, $p->get_tag(), "The first found tag was not '$rcdata_tag'" ); + $this->assertSame( strtoupper( $rcdata_tag ), $p->get_tag(), "The first found tag was not '$rcdata_tag'" ); $p->next_tag(); $this->assertSame( 'DIV', $p->get_tag(), "The second found tag was not 'div'" ); } diff --git a/phpunit/html/wp-html-tag-processor-wp-test.php b/phpunit/html/wp-html-tag-processor-wp-test.php deleted file mode 100644 index 41008800d0b751..00000000000000 --- a/phpunit/html/wp-html-tag-processor-wp-test.php +++ /dev/null @@ -1,91 +0,0 @@ - - * $p = new WP_HTML_Tag_Processor( '
' ); - * $p->next_tag(); - * $p->set_attribute('class', '" onclick="alert'); - * echo $p; - * //
- * - * - * To prevent it, `set_attribute` calls `esc_attr()` on its given values. - * - * - *
- *
- * - * @ticket 56299 - * - * @dataProvider data_set_attribute_escapable_values - * @covers set_attribute - */ - public function test_set_attribute_prevents_xss( $value_to_set, $expected_result ) { - $p = new WP_HTML_Tag_Processor( '
' ); - $p->next_tag(); - $p->set_attribute( 'test', $value_to_set ); - - /* - * Testing the escaping is hard using tools that properly parse - * HTML because they might interpret the escaped values. It's hard - * with tools that don't understand HTML because they might get - * confused by improperly-escaped values. - * - * For this test, since we control the input HTML we're going to - * do what looks like the opposite of what we want to be doing with - * this library but are only doing so because we have full control - * over the content and because we want to look at the raw values. - */ - $match = null; - preg_match( '~^
$~', $p->get_updated_html(), $match ); - list( , $actual_value ) = $match; - - $this->assertEquals( $actual_value, '"' . $expected_result . '"' ); - } - - /** - * Data provider with HTML attribute values that might need escaping. - */ - public function data_set_attribute_escapable_values() { - return array( - array( '"', '"' ), - array( '"', '"' ), - array( '&', '&' ), - array( '&', '&' ), - array( '€', '€' ), - array( "'", ''' ), - array( '<>', '<>' ), - array( '"";', '&quot";' ), - array( - '" onclick="alert(\'1\');">', - '" onclick="alert('1');"><span onclick=""></span><script>alert("1")</script>', - ), - ); - } - -} From fecc1d2aa66c2fb2c6379fb47de5d7cd71f01f8c Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 17 Nov 2022 11:28:16 +0400 Subject: [PATCH 06/76] Post Featured Image: Only get the post title when rendering alt text (#45835) --- packages/block-library/src/post-featured-image/index.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/block-library/src/post-featured-image/index.php b/packages/block-library/src/post-featured-image/index.php index 068b7eeac6899f..dfb03c5a89065c 100644 --- a/packages/block-library/src/post-featured-image/index.php +++ b/packages/block-library/src/post-featured-image/index.php @@ -27,12 +27,11 @@ function render_block_core_post_featured_image( $attributes, $content, $block ) $is_link = isset( $attributes['isLink'] ) && $attributes['isLink']; $size_slug = isset( $attributes['sizeSlug'] ) ? $attributes['sizeSlug'] : 'post-thumbnail'; - $post_title = trim( strip_tags( get_the_title( $post_ID ) ) ); $attr = get_block_core_post_featured_image_border_attributes( $attributes ); $overlay_markup = get_block_core_post_featured_image_overlay_element_markup( $attributes ); if ( $is_link ) { - $attr['alt'] = $post_title; + $attr['alt'] = trim( strip_tags( get_the_title( $post_ID ) ) ); } if ( ! empty( $attributes['height'] ) ) { From 476ce0377ab30055534b59837b5302068d27c788 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 17 Nov 2022 17:51:13 +1000 Subject: [PATCH 07/76] Sidebar: Split block tools into menu, settings, and appearance tabs (#45005) * Add drawer icons to icons package Switch editor sidebar icons to new drawer icons Icon should reflect RTL as well. Update TabPanel to allow icons on TabButtons Add menu group to InspectorControl slot fills Move nav menu controls into InspectorControls menu group Introduce menu, settings & appearance tabs to sidebar Refactor InspectorControlTabs Re-orders the appearance and settings tabs. Also now omits the TabPanel altogether if only a single tab has contents. Make block inspector tabs a Gutenberg experiment A little tidy up Clean up conditional tabs display Remove nav specific menu tab for now Remove Menu inspector controls group Refactor inspector controls tabs to components Remove conditional display of tabs Render no settings or tools messages when no fills Reduce new styles for equal width tabs Add general slot for items applying to block as a whole Move query block new post link to new slot Revert "Move query block new post link to new slot" This reverts commit 12799852648a67cb566112639d4d71e33a2d9834. Revert "Add general slot for items applying to block as a whole" This reverts commit 186276fb42af1a7dd571892b349eaacd34f0a3b0. Tweak no style options message Add changelog for TabPanel updates Remove empty readme until experiment settles Fix copy and paste error * Fix changelog Co-authored-by: Daniel Richards --- lib/experimental/editor-settings.php | 3 + lib/experiments-page.php | 14 ++ .../src/components/block-inspector/index.js | 163 +++++++++--------- .../src/components/block-inspector/style.scss | 10 +- .../advanced-controls-panel.js | 37 ++++ .../inspector-controls-tabs/appearance-tab.js | 75 ++++++++ .../inspector-controls-tabs/index.js | 42 +++++ .../inspector-controls-tabs/settings-tab.js | 43 +++++ .../inspector-controls-tabs/utils.js | 20 +++ packages/components/CHANGELOG.md | 4 + packages/components/src/tab-panel/README.md | 1 + packages/components/src/tab-panel/index.tsx | 5 +- packages/components/src/tab-panel/types.ts | 10 +- .../sidebar/settings-sidebar/index.js | 10 +- .../src/components/sidebar-edit-mode/index.js | 6 +- packages/icons/src/index.js | 2 + packages/icons/src/library/drawer-left.js | 21 +++ packages/icons/src/library/drawer-right.js | 21 +++ 18 files changed, 397 insertions(+), 90 deletions(-) create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/advanced-controls-panel.js create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/appearance-tab.js create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/index.js create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/utils.js create mode 100644 packages/icons/src/library/drawer-left.js create mode 100644 packages/icons/src/library/drawer-right.js diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index b0c05544d6b199..862a1bfe2b28c6 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -86,6 +86,9 @@ function gutenberg_enable_experiments() { if ( $gutenberg_experiments && array_key_exists( 'gutenberg-off-canvas-navigation-editor', $gutenberg_experiments ) ) { wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableOffCanvasNavigationEditor = true', 'before' ); } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-block-inspector-tabs', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableBlockInspectorTabs = true', 'before' ); + } } add_action( 'admin_init', 'gutenberg_enable_experiments' ); diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 309612664cb9c9..1296cdd03b89fa 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -52,6 +52,7 @@ function gutenberg_initialize_experiments_settings() { 'id' => 'gutenberg-zoomed-out-view', ) ); + add_settings_field( 'gutenberg-off-canvas-navigation-editor', __( 'Off canvas navigation editor ', 'gutenberg' ), @@ -63,6 +64,7 @@ function gutenberg_initialize_experiments_settings() { 'id' => 'gutenberg-off-canvas-navigation-editor', ) ); + add_settings_field( 'gutenberg-color-randomizer', __( 'Color randomizer ', 'gutenberg' ), @@ -75,6 +77,18 @@ function gutenberg_initialize_experiments_settings() { ) ); + add_settings_field( + 'gutenberg-block-inspector-tabs', + __( 'Block inspector tabs ', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Test a new block inspector view splitting settings and appearance controls into tabs', 'gutenberg' ), + 'id' => 'gutenberg-block-inspector-tabs', + ) + ); + register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 379d8806f2a68c..f48819ec4a02b6 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -9,9 +9,8 @@ import { store as blocksStore, } from '@wordpress/blocks'; import { - PanelBody, - __experimentalUseSlotFills as useSlotFills, FlexItem, + PanelBody, __experimentalHStack as HStack, __experimentalVStack as VStack, Button, @@ -24,20 +23,19 @@ import { useMemo, useCallback } from '@wordpress/element'; */ import SkipToSelectedBlock from '../skip-to-selected-block'; import BlockCard from '../block-card'; -import { - default as InspectorControls, - InspectorAdvancedControls, -} from '../inspector-controls'; -import BlockStyles from '../block-styles'; import MultiSelectionInspector from '../multi-selection-inspector'; -import DefaultStylePicker from '../default-style-picker'; import BlockVariationTransforms from '../block-variation-transforms'; import useBlockDisplayInformation from '../use-block-display-information'; import { store as blockEditorStore } from '../../store'; import BlockIcon from '../block-icon'; +import BlockStyles from '../block-styles'; +import DefaultStylePicker from '../default-style-picker'; +import { default as InspectorControls } from '../inspector-controls'; +import { default as InspectorControlsTabs } from '../inspector-controls-tabs'; +import AdvancedControls from '../inspector-controls-tabs/advanced-controls-panel'; function useContentBlocks( blockTypes, block ) { - const contenBlocksObjectAux = useMemo( () => { + const contentBlocksObjectAux = useMemo( () => { return blockTypes.reduce( ( result, blockType ) => { if ( blockType.name !== 'core/list-item' && @@ -53,7 +51,7 @@ function useContentBlocks( blockTypes, block ) { }, [ blockTypes ] ); const isContentBlock = useCallback( ( blockName ) => { - return !! contenBlocksObjectAux[ blockName ]; + return !! contentBlocksObjectAux[ blockName ]; }, [ blockTypes ] ); @@ -166,28 +164,36 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { }; }, [] ); + const showTabs = window?.__experimentalEnableBlockInspectorTabs; + if ( count > 1 ) { return (
- - - - - + { showTabs ? ( + + ) : ( + <> + + + + + + + ) }
); } @@ -229,6 +235,8 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { }; const BlockInspectorSingleBlock = ( { clientId, blockName } ) => { + const showTabs = window?.__experimentalEnableBlockInspectorTabs; + const hasBlockStyles = useSelect( ( select ) => { const { getBlockStyles } = select( blocksStore ); @@ -238,67 +246,64 @@ const BlockInspectorSingleBlock = ( { clientId, blockName } ) => { [ blockName ] ); const blockInformation = useBlockDisplayInformation( clientId ); + return (
- { hasBlockStyles && ( -
- - - { hasBlockSupport( - blockName, - 'defaultStylePicker', - true - ) && } - -
+ { showTabs && ( + + ) } + { ! showTabs && ( + <> + { hasBlockStyles && ( +
+ + + { hasBlockSupport( + blockName, + 'defaultStylePicker', + true + ) && ( + + ) } + +
+ ) } + + + + + +
+ +
+ ) } - - - - - -
- -
); }; -const AdvancedControls = () => { - const fills = useSlotFills( InspectorAdvancedControls.slotName ); - const hasFills = Boolean( fills && fills.length ); - - if ( ! hasFills ) { - return null; - } - - return ( - - - - ); -}; - /** * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-inspector/README.md */ diff --git a/packages/block-editor/src/components/block-inspector/style.scss b/packages/block-editor/src/components/block-inspector/style.scss index b7cfdcf46333a2..08ca013629927e 100644 --- a/packages/block-editor/src/components/block-inspector/style.scss +++ b/packages/block-editor/src/components/block-inspector/style.scss @@ -27,7 +27,8 @@ } } -.block-editor-block-inspector__no-blocks { +.block-editor-block-inspector__no-blocks, +.block-editor-block-inspector__no-block-tools { display: block; font-size: $default-font-size; background: $white; @@ -35,6 +36,13 @@ text-align: center; } +.block-editor-block-inspector__no-block-tools { + border-top: $border-width solid $gray-300; +} + +.block-editor-block-inspector__tab-item { + flex: 1 1 0px; +} .block-editor-block-inspector__block-buttons-container { border-top: $border-width solid $gray-200; diff --git a/packages/block-editor/src/components/inspector-controls-tabs/advanced-controls-panel.js b/packages/block-editor/src/components/inspector-controls-tabs/advanced-controls-panel.js new file mode 100644 index 00000000000000..83027861e9d190 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/advanced-controls-panel.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { + PanelBody, + __experimentalUseSlotFills as useSlotFills, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { + default as InspectorControls, + InspectorAdvancedControls, +} from '../inspector-controls'; + +const AdvancedControls = () => { + const fills = useSlotFills( InspectorAdvancedControls.slotName ); + const hasFills = Boolean( fills && fills.length ); + + if ( ! hasFills ) { + return null; + } + + return ( + + + + ); +}; + +export default AdvancedControls; diff --git a/packages/block-editor/src/components/inspector-controls-tabs/appearance-tab.js b/packages/block-editor/src/components/inspector-controls-tabs/appearance-tab.js new file mode 100644 index 00000000000000..deb3bccffaa168 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/appearance-tab.js @@ -0,0 +1,75 @@ +/** + * WordPress dependencies + */ +import { hasBlockSupport } from '@wordpress/blocks'; +import { + PanelBody, + __experimentalUseSlotFills as useSlotFills, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import BlockStyles from '../block-styles'; +import DefaultStylePicker from '../default-style-picker'; +import InspectorControls from '../inspector-controls'; +import InspectorControlsGroups from '../inspector-controls/groups'; + +const AppearanceTab = ( { + blockName, + clientId, + hasBlockStyles, + hasSingleBlockSelection = false, +} ) => { + const { border, color, dimensions, typography } = InspectorControlsGroups; + const appearanceFills = [ + ...( useSlotFills( border.Slot.__unstableName ) || [] ), + ...( useSlotFills( color.Slot.__unstableName ) || [] ), + ...( useSlotFills( dimensions.Slot.__unstableName ) || [] ), + ...( useSlotFills( typography.Slot.__unstableName ) || [] ), + ]; + + return ( + <> + { ! appearanceFills.length && ( + + { hasSingleBlockSelection + ? __( 'This block has no style options.' ) + : __( 'The selected blocks have no style options.' ) } + + ) } + { hasBlockStyles && ( +
+ + + { hasBlockSupport( + blockName, + 'defaultStylePicker', + true + ) && } + +
+ ) } + + + + + + ); +}; + +export default AppearanceTab; diff --git a/packages/block-editor/src/components/inspector-controls-tabs/index.js b/packages/block-editor/src/components/inspector-controls-tabs/index.js new file mode 100644 index 00000000000000..62b87dd58e11cf --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/index.js @@ -0,0 +1,42 @@ +/** + * WordPress dependencies + */ +import { TabPanel } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { TAB_SETTINGS, TAB_APPEARANCE } from './utils'; +import AppearanceTab from './appearance-tab'; +import SettingsTab from './settings-tab'; + +const tabs = [ TAB_APPEARANCE, TAB_SETTINGS ]; + +export default function InspectorControlsTabs( { + blockName, + clientId, + hasBlockStyles, +} ) { + return ( + + { ( tab ) => { + if ( tab.name === TAB_SETTINGS.name ) { + return ( + + ); + } + + if ( tab.name === TAB_APPEARANCE.name ) { + return ( + + ); + } + } } + + ); +} diff --git a/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js b/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js new file mode 100644 index 00000000000000..a000173807c197 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js @@ -0,0 +1,43 @@ +/** + * WordPress dependencies + */ +import { __experimentalUseSlotFills as useSlotFills } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import AdvancedControls from './advanced-controls-panel'; +import InspectorControlsGroups from '../inspector-controls/groups'; +import { + default as InspectorControls, + InspectorAdvancedControls, +} from '../inspector-controls'; + +const SettingsTab = ( { hasSingleBlockSelection = false } ) => { + const { default: defaultGroup } = InspectorControlsGroups; + const settingsFills = [ + ...( useSlotFills( defaultGroup.Slot.__unstableName ) || [] ), + ...( useSlotFills( InspectorAdvancedControls.slotName ) || [] ), + ]; + + return ( + <> + + { hasSingleBlockSelection && ( +
+ +
+ ) } + { ! settingsFills.length && ( + + { hasSingleBlockSelection + ? __( 'This block has no settings.' ) + : __( 'The selected blocks have no settings.' ) } + + ) } + + ); +}; + +export default SettingsTab; diff --git a/packages/block-editor/src/components/inspector-controls-tabs/utils.js b/packages/block-editor/src/components/inspector-controls-tabs/utils.js new file mode 100644 index 00000000000000..0bec1088174d31 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/utils.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { cog, styles } from '@wordpress/icons'; + +export const TAB_SETTINGS = { + name: 'settings', + title: 'Settings', + value: 'settings', + icon: cog, + className: 'block-editor-block-inspector__tab-item', +}; + +export const TAB_APPEARANCE = { + name: 'appearance', + title: 'Appearance', + value: 'appearance', + icon: styles, + className: 'block-editor-block-inspector__tab-item', +}; diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 62169314631bda..7f740706518c2e 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancements + +- `TabPanel`: Add ability to set icon only tab buttons ([#45005](https://github.com/WordPress/gutenberg/pull/45005)). + ## 22.1.0 (2022-11-16) ### Enhancements diff --git a/packages/components/src/tab-panel/README.md b/packages/components/src/tab-panel/README.md index a4a1c0c525370e..c3e889a2bbf33d 100644 --- a/packages/components/src/tab-panel/README.md +++ b/packages/components/src/tab-panel/README.md @@ -120,6 +120,7 @@ An array of objects containing the following properties: - `name`: `(string)` Defines the key for the tab. - `title`:`(string)` Defines the translated text for the tab. - `className`:`(string)` Optional. Defines the class to put on the tab. +- `icon`:`(ReactNode)` Optional. When set, displays the icon in place of the tab title. The title is then rendered as an aria-label and tooltip. > > **Note:** Other fields may be added to the object and accessed from the child function if desired. diff --git a/packages/components/src/tab-panel/index.tsx b/packages/components/src/tab-panel/index.tsx index 206c2b8aab0a71..dfd3ebd9455190 100644 --- a/packages/components/src/tab-panel/index.tsx +++ b/packages/components/src/tab-panel/index.tsx @@ -127,8 +127,11 @@ export function TabPanel( { selected={ tab.name === selected } key={ tab.name } onClick={ () => handleTabSelection( tab.name ) } + label={ tab.icon && tab.title } + icon={ tab.icon } + showTooltip={ !! tab.icon } > - { tab.title } + { ! tab.icon && tab.title } ) ) } diff --git a/packages/components/src/tab-panel/types.ts b/packages/components/src/tab-panel/types.ts index 2a89da6a215b7d..1436b8034a66af 100644 --- a/packages/components/src/tab-panel/types.ts +++ b/packages/components/src/tab-panel/types.ts @@ -3,6 +3,11 @@ */ import type { ReactNode } from 'react'; +/** + * Internal dependencies + */ +import type { IconType } from '../icon'; + type Tab = { /** * The key of the tab. @@ -18,11 +23,14 @@ type Tab = { className?: string; } & Record< any, any >; -export type TabButtonProps = { +export type TabButtonProps< IconProps = unknown > = { children: ReactNode; className?: string; + icon?: IconType< IconProps >; + label?: string; onClick: ( event: MouseEvent ) => void; selected: boolean; + showTooltip?: boolean; tabId: string; }; diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index c044d0b49714cd..677161c6b5a383 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -5,10 +5,12 @@ import { BlockInspector, store as blockEditorStore, } from '@wordpress/block-editor'; -import { cog } from '@wordpress/icons'; +import { useSelect } from '@wordpress/data'; import { Platform } from '@wordpress/element'; -import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { isRTL, __ } from '@wordpress/i18n'; +import { drawerLeft, drawerRight } from '@wordpress/icons'; import { store as interfaceStore } from '@wordpress/interface'; +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; /** * Internal dependencies @@ -25,8 +27,6 @@ import MetaBoxes from '../../meta-boxes'; import PluginDocumentSettingPanel from '../plugin-document-setting-panel'; import PluginSidebarEditPost from '../plugin-sidebar'; import TemplateSummary from '../template-summary'; -import { __ } from '@wordpress/i18n'; -import { useSelect } from '@wordpress/data'; import { store as editPostStore } from '../../../store'; const SIDEBAR_ACTIVE_BY_DEFAULT = Platform.select( { @@ -78,7 +78,7 @@ const SettingsSidebar = () => { /* translators: button label text should, if possible, be under 16 characters. */ title={ __( 'Settings' ) } toggleShortcut={ keyboardShortcut } - icon={ cog } + icon={ isRTL() ? drawerLeft : drawerRight } isActiveByDefault={ SIDEBAR_ACTIVE_BY_DEFAULT } > { ! isTemplateMode && sidebarName === 'edit-post/document' && ( diff --git a/packages/edit-site/src/components/sidebar-edit-mode/index.js b/packages/edit-site/src/components/sidebar-edit-mode/index.js index 080be432eb09d8..12073a77b56e03 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/index.js @@ -2,8 +2,8 @@ * WordPress dependencies */ import { createSlotFill, PanelBody } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { cog } from '@wordpress/icons'; +import { isRTL, __ } from '@wordpress/i18n'; +import { drawerLeft, drawerRight } from '@wordpress/icons'; import { useEffect, Fragment } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as interfaceStore } from '@wordpress/interface'; @@ -78,7 +78,7 @@ export function SidebarComplementaryAreaFills() { } headerClassName="edit-site-sidebar-edit-mode__panel-tabs" diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js index c7f891a5292dbf..5870ccba9ab043 100644 --- a/packages/icons/src/index.js +++ b/packages/icons/src/index.js @@ -66,6 +66,8 @@ export { default as currencyPound } from './library/currency-pound'; export { default as customPostType } from './library/custom-post-type'; export { default as desktop } from './library/desktop'; export { default as dragHandle } from './library/drag-handle'; +export { default as drawerLeft } from './library/drawer-left'; +export { default as drawerRight } from './library/drawer-right'; export { default as download } from './library/download'; export { default as edit } from './library/edit'; export { default as external } from './library/external'; diff --git a/packages/icons/src/library/drawer-left.js b/packages/icons/src/library/drawer-left.js new file mode 100644 index 00000000000000..2e1626fb1fe17a --- /dev/null +++ b/packages/icons/src/library/drawer-left.js @@ -0,0 +1,21 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const drawerLeft = ( + + + +); + +export default drawerLeft; diff --git a/packages/icons/src/library/drawer-right.js b/packages/icons/src/library/drawer-right.js new file mode 100644 index 00000000000000..95a8e72f775fb1 --- /dev/null +++ b/packages/icons/src/library/drawer-right.js @@ -0,0 +1,21 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const drawerRight = ( + + + +); + +export default drawerRight; From 7147465adf207a3aa6717a882d504d33934702a9 Mon Sep 17 00:00:00 2001 From: Corey Worrell Date: Thu, 17 Nov 2022 00:17:54 -0800 Subject: [PATCH 08/76] Fix for navigation anchor links to close modal (#45829) * Fix for navigation anchor links to close modal * Rename function to be more accurate --- packages/block-library/src/navigation/view-modal.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation/view-modal.js b/packages/block-library/src/navigation/view-modal.js index 477f05b5d5e51e..9477d262816d93 100644 --- a/packages/block-library/src/navigation/view-modal.js +++ b/packages/block-library/src/navigation/view-modal.js @@ -27,6 +27,16 @@ function navigationToggleModal( modal ) { htmlElement.classList.toggle( 'has-modal-open' ); } +function isLinkToAnchorOnCurrentPage( node ) { + return ( + node.hash && + node.protocol === window.location.protocol && + node.host === window.location.host && + node.pathname === window.location.pathname && + node.search === window.location.search + ); +} + window.addEventListener( 'load', () => { MicroModal.init( { onShow: navigationToggleModal, @@ -42,7 +52,7 @@ window.addEventListener( 'load', () => { navigationLinks.forEach( function ( link ) { // Ignore non-anchor links and anchor links which open on a new tab. if ( - ! link.getAttribute( 'href' )?.startsWith( '#' ) || + ! isLinkToAnchorOnCurrentPage( link ) || link.attributes?.target === '_blank' ) { return; From 7ec665c6046ef6fbe326a3f71e872b2b332b32f5 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 17 Nov 2022 11:40:04 +0100 Subject: [PATCH 09/76] LinkControl unit tests: use user.type to type into search field (#45802) --- .../src/components/link-control/test/index.js | 88 +++++++------------ 1 file changed, 31 insertions(+), 57 deletions(-) diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index f5afb7e144f13c..e7a65a0743a192 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -186,8 +186,7 @@ describe( 'Basic rendering', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); expect( screen.queryByText( '://' ) ).not.toBeInTheDocument(); } ); @@ -328,8 +327,7 @@ describe( 'Searching for a link', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -364,8 +362,7 @@ describe( 'Searching for a link', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -406,8 +403,7 @@ describe( 'Searching for a link', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -447,8 +443,7 @@ describe( 'Searching for a link', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( 'anything' ); + await user.type( searchInput, 'anything' ); const searchResultsField = screen.queryByRole( 'listbox' ); @@ -472,8 +467,7 @@ describe( 'Searching for a link', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -510,8 +504,7 @@ describe( 'Searching for a link', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( 'couldbeurlorentitysearchterm' ); + await user.type( searchInput, 'couldbeurlorentitysearchterm' ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -544,8 +537,7 @@ describe( 'Manual link entry', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -591,13 +583,12 @@ describe( 'Manual link entry', () => { expect( submitButton ).toBeDisabled(); expect( submitButton ).toBeVisible(); - searchInput.focus(); if ( searchString.length ) { // Simulate searching for a term. - await user.keyboard( searchString ); + await user.type( searchInput, searchString ); } else { // Simulate clearing the search term. - await userEvent.clear( searchInput ); + await user.clear( searchInput ); } // fetchFauxEntitySuggestions resolves on next "tick" of event loop. @@ -637,13 +628,12 @@ describe( 'Manual link entry', () => { expect( submitButton ).toBeVisible(); // Simulate searching for a term. - searchInput.focus(); if ( searchString.length ) { // Simulate searching for a term. - await user.keyboard( searchString ); + await user.type( searchInput, searchString ); } else { // Simulate clearing the search term. - await userEvent.clear( searchInput ); + await user.clear( searchInput ); } // fetchFauxEntitySuggestions resolves on next "tick" of event loop. @@ -681,8 +671,7 @@ describe( 'Manual link entry', () => { } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -796,8 +785,7 @@ describe( 'Default search suggestions', () => { searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -911,8 +899,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( entityNameText ); + await user.type( searchInput, entityNameText ); await eventLoopTick(); @@ -983,8 +970,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( 'Some new page to create' ); + await user.type( searchInput, 'Some new page to create' ); await eventLoopTick(); @@ -1039,8 +1025,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( entityNameText ); + await user.type( searchInput, entityNameText ); await eventLoopTick(); @@ -1049,18 +1034,16 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { name: /Search results for.*/, } ) ).getAllByRole( 'option' ); - const createButton = Array.from( searchResultElements ).filter( - ( result ) => result.innerHTML.includes( 'Create:' ) - )[ 0 ]; + const createButton = searchResultElements.find( ( result ) => + result.innerHTML.includes( 'Create:' ) + ); // Step down into the search results, highlighting the first result item. triggerArrowDown( searchInput ); - createButton.focus(); - await user.keyboard( '[Enter]' ); + await user.click( createButton ); - searchInput.focus(); - await user.keyboard( '[Enter]' ); + await user.type( searchInput, '[Enter]' ); await eventLoopTick(); @@ -1088,8 +1071,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( entityNameText ); + await user.type( searchInput, entityNameText ); await eventLoopTick(); @@ -1167,8 +1149,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( inputText ); + await user.type( searchInput, inputText ); await eventLoopTick(); @@ -1205,8 +1186,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchText ); + await user.type( searchInput, searchText ); await eventLoopTick(); @@ -1340,8 +1320,7 @@ describe( 'Selecting links', () => { } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -1406,8 +1385,7 @@ describe( 'Selecting links', () => { } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -1626,8 +1604,7 @@ describe( 'Post types', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -1657,8 +1634,7 @@ describe( 'Post types', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -2050,8 +2026,7 @@ describe( 'Controlling link title text', () => { const textInput = screen.queryByRole( 'textbox', { name: 'Text' } ); - textInput.focus(); - await userEvent.clear( textInput ); + await user.clear( textInput ); await user.keyboard( textValue ); expect( textInput ).toHaveValue( textValue ); @@ -2087,8 +2062,7 @@ describe( 'Controlling link title text', () => { expect( textInput ).toBeVisible(); - textInput.focus(); - await userEvent.clear( textInput ); + await user.clear( textInput ); await user.keyboard( textValue ); // Attempt to submit the empty search value in the input. From dd166c113d38d28b372edf5b97f8143cc22cd04f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Thu, 17 Nov 2022 04:11:34 -0700 Subject: [PATCH 10/76] Update `wp_theme_has_theme_json` to use `WP_Object_Cache` (#45543) Co-authored-by: Felix Arntz Co-authored-by: Miguel Torres Co-authored-by: Jonny Harris --- lib/compat/wordpress-6.2/default-filters.php | 11 +--- .../get-global-styles-and-settings.php | 66 ++++++++++++++----- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/lib/compat/wordpress-6.2/default-filters.php b/lib/compat/wordpress-6.2/default-filters.php index ac1ce19b425512..860cf9a8bf412a 100644 --- a/lib/compat/wordpress-6.2/default-filters.php +++ b/lib/compat/wordpress-6.2/default-filters.php @@ -17,11 +17,6 @@ * @package gutenberg */ -/** - * Note for backport: we should also remove the existing filters: - * - * > add_action( 'switch_theme', array( 'WP_Theme_JSON_Resolver', 'clean_cached_data' ) ); - * > add_action( 'start_previewing_theme', array( 'WP_Theme_JSON_Resolver', 'clean_cached_data' ) ); - */ -add_action( 'switch_theme', 'wp_theme_clean_theme_json_cached_data' ); -add_action( 'start_previewing_theme', 'wp_theme_clean_theme_json_cached_data' ); +add_action( 'switch_theme', 'wp_theme_has_theme_json_clean_cache' ); +add_action( 'start_previewing_theme', 'wp_theme_has_theme_json_clean_cache' ); +add_action( 'upgrader_process_complete', '_wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme' ); diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index 4dbd9d0ba8bde1..b2637969fd8ef6 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -9,39 +9,69 @@ /** * Whether a theme or its parent have a theme.json file. * - * @param boolean $clear_cache Whether the cache should be cleared and theme support recomputed. Default is false. + * The result would be cached via the WP_Object_Cache. + * It can be cleared by calling wp_theme_has_theme_json_clean_cache(). * * @return boolean */ - function wp_theme_has_theme_json( $clear_cache = false ) { - static $theme_has_support = null; + function wp_theme_has_theme_json() { + $cache_group = 'theme_json'; + $cache_key = 'wp_theme_has_theme_json'; + $theme_has_support = wp_cache_get( $cache_key, $cache_group ); - if ( true === $clear_cache ) { - $theme_has_support = null; - } - - if ( null !== $theme_has_support ) { - return $theme_has_support; + /** + * $theme_has_support is stored as a int in the cache. + * + * The reason not to store it as a boolean is to avoid working + * with the $found parameter which apparently had some issues in some implementations + * https://developer.wordpress.org/reference/functions/wp_cache_get/ + */ + if ( 0 === $theme_has_support || 1 === $theme_has_support ) { + return (bool) $theme_has_support; } // Has the own theme a theme.json? - $theme_has_support = is_readable( get_stylesheet_directory() . '/theme.json' ); + $theme_has_support = is_readable( get_stylesheet_directory() . '/theme.json' ) ? 1 : 0; // Look up the parent if the child does not have a theme.json. - if ( ! $theme_has_support ) { - $theme_has_support = is_readable( get_template_directory() . '/theme.json' ); + if ( 0 === $theme_has_support ) { + $theme_has_support = is_readable( get_template_directory() . '/theme.json' ) ? 1 : 0; } - return $theme_has_support; + wp_cache_set( $cache_key, $theme_has_support, $cache_group ); + + return (bool) $theme_has_support; + } +} + +if ( ! function_exists( 'wp_theme_has_theme_json_clean_cache' ) ) { + /** + * Function to clean the cache used by wp_theme_has_theme_json method. + */ + function wp_theme_has_theme_json_clean_cache() { + wp_cache_delete( 'wp_theme_has_theme_json', 'theme_json' ); } } -if ( ! function_exists( 'wp_theme_clean_theme_json_cached_data' ) ) { +if ( ! function_exists( '_wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme' ) ) { /** - * Clean theme.json related cached data. + * Private function to clean the cache used by wp_theme_has_theme_json method. + * + * It is hooked into the `upgrader_process_complete` action. + * + * @see default-filters.php + * + * @param WP_Upgrader $upgrader Instance of WP_Upgrader class. + * @param array $options Metadata that identifies the data that is updated. */ - function wp_theme_clean_theme_json_cached_data() { - wp_theme_has_theme_json( true ); - WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data(); + function _wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme( $upgrader, $options ) { + // The cache only needs cleaning when the active theme was updated. + if ( + 'update' === $options['action'] && + 'theme' === $options['type'] && + ( isset( $options['themes'][ get_stylesheet() ] ) || isset( $options['themes'][ get_template() ] ) ) + ) { + wp_theme_has_theme_json_clean_cache(); + } } } From 95c2b1311590517f279a261ef6eff6eab3bddfad Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Thu, 17 Nov 2022 11:46:45 +0000 Subject: [PATCH 11/76] Navigation: adds a warning about duplicate code for the future (#45844) --- packages/block-library/src/navigation-link/edit.js | 1 + packages/block-library/src/navigation-submenu/edit.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index ccd0aae57bcbbf..ed0acced4efe88 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -714,6 +714,7 @@ export default function NavigationLinkEdit( { ) } + { /* Warning, this duplicated in packages/block-library/src/navigation-submenu/edit.js */ } + { /* Warning, this duplicated in packages/block-library/src/navigation-link/edit.js */ } Date: Thu, 17 Nov 2022 15:08:16 +0100 Subject: [PATCH 12/76] Small refactoring to the NavigableRegion component (#45849) --- .../src/components/interface-skeleton/index.js | 16 +++++++++------- .../src/components/navigable-region/index.js | 12 +++--------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/interface/src/components/interface-skeleton/index.js b/packages/interface/src/components/interface-skeleton/index.js index 560943aa5d9634..f7d5ab36566e30 100644 --- a/packages/interface/src/components/interface-skeleton/index.js +++ b/packages/interface/src/components/interface-skeleton/index.js @@ -7,7 +7,10 @@ import classnames from 'classnames'; * WordPress dependencies */ import { forwardRef, useEffect } from '@wordpress/element'; -import { __unstableUseNavigateRegions as useNavigateRegions } from '@wordpress/components'; +import { + __unstableUseNavigateRegions as useNavigateRegions, + __unstableMotion as motion, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useMergeRefs } from '@wordpress/compose'; @@ -101,14 +104,13 @@ function InterfaceSkeleton(
{ !! header && isDistractionFree && ( { header } diff --git a/packages/interface/src/components/navigable-region/index.js b/packages/interface/src/components/navigable-region/index.js index 3b86b856b1d3f6..31037ffd333546 100644 --- a/packages/interface/src/components/navigable-region/index.js +++ b/packages/interface/src/components/navigable-region/index.js @@ -3,26 +3,20 @@ */ import classnames from 'classnames'; -/** - * WordPress dependencies - */ -import { __unstableMotion as motion } from '@wordpress/components'; - export default function NavigableRegion( { children, className, ariaLabel, - motionProps = {}, + as: Tag = 'div', + ...props } ) { - const Tag = Object.keys( motionProps ).length ? motion.div : 'div'; - return (
{ children } From 49d03013815da46014f4316f3b9e5434047b72cf Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Sat, 12 Nov 2022 15:57:28 -0700 Subject: [PATCH 13/76] Performance Test: Reuse tests-branch build if possible When running the performance test suite we currently build three copies of the plugin: one for each branch under test and also one for the branch which provides the actual test suite. In this patch we're checking first if one of the branches under test is the same as that tests branch, and if so, re-using the built files from that to avoid the additional approximately five minutes of building. --- bin/plugin/commands/performance.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index 7101dc65118089..b55618cbfd1801 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -260,12 +260,23 @@ async function runPerformanceTests( branches, options ) { await runShellScript( 'mkdir ' + environmentDirectory ); await runShellScript( `cp -R ${ baseDirectory } ${ buildPath }` ); - log( ` >> Fetching the ${ formats.success( branch ) } branch` ); - // @ts-ignore - await SimpleGit( buildPath ).reset( 'hard' ).checkout( branch ); + const fancyBranch = formats.success( branch ); - log( ` >> Building the ${ formats.success( branch ) } branch` ); - await runShellScript( 'npm ci && npm run build', buildPath ); + if ( branch === options.testsBranch ) { + log( + ` >> Re-using the testing branch for ${ fancyBranch }` + ); + await runShellScript( + `cp -R ${ performanceTestDirectory } ${ buildPath }` + ); + } else { + log( ` >> Fetching the ${ fancyBranch } branch` ); + // @ts-ignore + await SimpleGit( buildPath ).reset( 'hard' ).checkout( branch ); + + log( ` >> Building the ${ fancyBranch } branch` ); + await runShellScript( 'npm ci && npm run build', buildPath ); + } await runShellScript( 'cp ' + From a71adfd1a23071fb707fd9cba27830c5c1e7c385 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 17 Nov 2022 17:08:42 +0000 Subject: [PATCH 14/76] List View - Stop child item selecting a parent which is already selected (#45860) --- .../block-settings-dropdown.js | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js index 87fc6bc5e0585e..9ebc961440f7a5 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js +++ b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js @@ -189,6 +189,11 @@ export function BlockSettingsDropdown( { }, } ); + // This can occur when the selected block (the parent) + // displays child blocks within a List View. + const parentBlockIsSelected = + selectedBlockClientIds?.includes( firstParentClientId ); + return ( - { !! firstParentClientId && ( - - } - onClick={ () => - selectBlock( firstParentClientId ) - } - > - { sprintf( - /* translators: %s: Name of the block's parent. */ - __( 'Select parent block (%s)' ), - parentBlockType.title - ) } - - ) } + { ! parentBlockIsSelected && + !! firstParentClientId && ( + + } + onClick={ () => + selectBlock( + firstParentClientId + ) + } + > + { sprintf( + /* translators: %s: Name of the block's parent. */ + __( + 'Select parent block (%s)' + ), + parentBlockType.title + ) } + + ) } { count === 1 && ( Date: Thu, 17 Nov 2022 17:16:16 +0000 Subject: [PATCH 15/76] Update Changelog for 14.5.3 --- changelog.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/changelog.txt b/changelog.txt index 548a18a5ef4642..809e3f5ebc6bc4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,30 @@ == Changelog == += 14.5.3 = + + + +## Changelog + +### Performance + +#### Global Styles +- Update `wp_theme_has_theme_json` to use `WP_Object_Cache`. ([45543](https://github.com/WordPress/gutenberg/pull/45543)) + + +## First time contributors + +The following PRs were merged by first time contributors: + + + +## Contributors + +The following contributors merged PRs in this release: + +@oandregal + + = 14.5.2 = From 8e83fffecc34fa919722a7bbcad93ce0571e7bb8 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Thu, 10 Nov 2022 17:52:43 -0700 Subject: [PATCH 16/76] Perf Tests: Stop building project types during build phase We don't use the types we build during the test runs, but generating them performs additional wasted work. Let's skip over that step to cut out unecessary delay and to avoid computation we don't need. --- bin/plugin/commands/performance.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index b55618cbfd1801..a7f9c81e8cebb3 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -241,7 +241,7 @@ async function runPerformanceTests( branches, options ) { log( ' >> Installing dependencies and building packages' ); await runShellScript( - 'npm ci && npm run build:packages', + 'npm ci && node ./bin/packages/build.js', performanceTestDirectory ); log( ' >> Creating the environment folders' ); @@ -275,7 +275,10 @@ async function runPerformanceTests( branches, options ) { await SimpleGit( buildPath ).reset( 'hard' ).checkout( branch ); log( ` >> Building the ${ fancyBranch } branch` ); - await runShellScript( 'npm ci && npm run build', buildPath ); + await runShellScript( + 'npm ci && node ./bin/packages/build.js', + buildPath + ); } await runShellScript( From b9da345fadf827e509025e8775614142cd10a392 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Fri, 18 Nov 2022 02:45:22 +0000 Subject: [PATCH 17/76] Navigation: Extract components (#45850) --- .../src/navigation/edit/index.js | 120 ++++++---------- .../edit/menu-inspector-controls.js | 135 ++++++++++++++++++ 2 files changed, 177 insertions(+), 78 deletions(-) create mode 100644 packages/block-library/src/navigation/edit/menu-inspector-controls.js diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 6cf5eea22e0a04..588f4397274bd9 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -14,7 +14,6 @@ import { useMemo, } from '@wordpress/element'; import { - __experimentalOffCanvasEditor as OffCanvasEditor, InspectorControls, useBlockProps, __experimentalRecursionProvider as RecursionProvider, @@ -37,8 +36,6 @@ import { __experimentalToggleGroupControlOption as ToggleGroupControlOption, Button, Spinner, - __experimentalHStack as HStack, - __experimentalHeading as Heading, } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; @@ -53,7 +50,6 @@ import useNavigationEntities from '../use-navigation-entities'; import Placeholder from './placeholder'; import ResponsiveWrapper from './responsive-wrapper'; import NavigationInnerBlocks from './inner-blocks'; -import NavigationMenuSelector from './navigation-menu-selector'; import NavigationMenuNameControl from './navigation-menu-name-control'; import UnsavedInnerBlocks from './unsaved-inner-blocks'; import NavigationMenuDeleteControl from './navigation-menu-delete-control'; @@ -69,6 +65,7 @@ import useCreateNavigationMenu from './use-create-navigation-menu'; import { useInnerBlocks } from './use-inner-blocks'; import { detectColors } from './utils'; import ManageMenusButton from './manage-menus-button'; +import MenuInspectorControls from './menu-inspector-controls'; function Navigation( { attributes, @@ -663,84 +660,26 @@ function Navigation( { // that automatically saves the menu as an entity when changes are made to the inner blocks. const hasUnsavedBlocks = hasUncontrolledInnerBlocks && ! isEntityAvailable; - const WrappedNavigationMenuSelector = ( { currentMenuId } ) => ( - { - handleUpdateMenu( menuId ); - } } - onSelectClassicMenu={ async ( classicMenu ) => { - const navMenu = await convertClassicMenu( - classicMenu.id, - classicMenu.name, - 'draft' - ); - if ( navMenu ) { - handleUpdateMenu( navMenu.id, { - focusNavigationBlock: true, - } ); - } - } } - onCreateNew={ createUntitledEmptyNavigationMenu } - createNavigationMenuIsSuccess={ createNavigationMenuIsSuccess } - createNavigationMenuIsError={ createNavigationMenuIsError } - /* translators: %s: The name of a menu. */ - actionLabel={ __( "Switch to '%s'" ) } - /> - ); - const isManageMenusButtonDisabled = ! hasManagePermissions || ! hasResolvedNavigationMenus; - const MenuInspectorControls = ( { currentMenuId = null } ) => ( - - - { isOffCanvasNavigationEditorEnabled ? ( - <> - - - { __( 'Menu' ) } - - - - { currentMenuId && isNavigationMenuMissing ? ( -

{ __( 'Select or create a menu' ) }

- ) : ( - - ) } - - ) : ( - <> - - - - ) } -
-
- ); - if ( hasUnsavedBlocks && ! isCreatingNavigationMenu ) { return ( - + { stylingInspectorControls } - + { __( 'Navigation menu has been deleted or is unavailable. ' @@ -845,7 +796,20 @@ function Navigation( { return ( - + { stylingInspectorControls } { isEntityAvailable && ( diff --git a/packages/block-library/src/navigation/edit/menu-inspector-controls.js b/packages/block-library/src/navigation/edit/menu-inspector-controls.js new file mode 100644 index 00000000000000..9f92d130ff14d0 --- /dev/null +++ b/packages/block-library/src/navigation/edit/menu-inspector-controls.js @@ -0,0 +1,135 @@ +/** + * WordPress dependencies + */ +import { + __experimentalOffCanvasEditor as OffCanvasEditor, + InspectorControls, +} from '@wordpress/block-editor'; +import { + PanelBody, + __experimentalHStack as HStack, + __experimentalHeading as Heading, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import ManageMenusButton from './manage-menus-button'; +import NavigationMenuSelector from './navigation-menu-selector'; + +const WrappedNavigationMenuSelector = ( { + clientId, + currentMenuId, + handleUpdateMenu, + convertClassicMenu, + onCreateNew, + createNavigationMenuIsSuccess, + createNavigationMenuIsError, +} ) => ( + { + handleUpdateMenu( menuId ); + } } + onSelectClassicMenu={ async ( classicMenu ) => { + const navMenu = await convertClassicMenu( + classicMenu.id, + classicMenu.name, + 'draft' + ); + if ( navMenu ) { + handleUpdateMenu( navMenu.id, { + focusNavigationBlock: true, + } ); + } + } } + onCreateNew={ onCreateNew } + createNavigationMenuIsSuccess={ createNavigationMenuIsSuccess } + createNavigationMenuIsError={ createNavigationMenuIsError } + /* translators: %s: The name of a menu. */ + actionLabel={ __( "Switch to '%s'" ) } + /> +); +const MenuInspectorControls = ( { + clientId, + convertClassicMenu, + createNavigationMenuIsSuccess, + createNavigationMenuIsError, + currentMenuId = null, + handleUpdateMenu, + isNavigationMenuMissing, + innerBlocks, + isManageMenusButtonDisabled, + onCreateNew, +} ) => { + const isOffCanvasNavigationEditorEnabled = + window?.__experimentalEnableOffCanvasNavigationEditor === true; + + return ( + + + { isOffCanvasNavigationEditorEnabled ? ( + <> + + + { __( 'Menu' ) } + + + + { currentMenuId && isNavigationMenuMissing ? ( +

{ __( 'Select or create a menu' ) }

+ ) : ( + + ) } + + ) : ( + <> + + + + ) } +
+
+ ); +}; + +export default MenuInspectorControls; From 2a2ecb319ef40ca4988a0923b6cd22b2d60bf645 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Fri, 18 Nov 2022 17:23:54 +0900 Subject: [PATCH 18/76] Storybook: Add link to component folder on GitHub, retire Storysource (#45727) * Storybook: Add source link button * Remove storysource addon * Update docs * Add link to relevant plugin --- package-lock.json | 153 +++++------------- package.json | 2 +- storybook/main.js | 2 +- storybook/preview.js | 1 + storybook/stories/docs/inline-icon.js | 14 ++ storybook/stories/docs/introduction.story.mdx | 20 +-- storybook/stories/playground/index.js | 3 + storybook/webpack.config.js | 9 ++ storybook/webpack/source-link-loader.js | 67 ++++++++ 9 files changed, 146 insertions(+), 125 deletions(-) create mode 100644 storybook/stories/docs/inline-icon.js create mode 100644 storybook/webpack/source-link-loader.js diff --git a/package-lock.json b/package-lock.json index 9f0f34dd3c5bf9..adb96b28da9216 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9831,87 +9831,6 @@ } } }, - "@storybook/addon-storysource": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-6.5.7.tgz", - "integrity": "sha512-4cquq0Oqf9skPdIYkVtO5lAPS7+83TyY4vT52HQ5LSpSekZELQkRxN793jNMQdu2IGQ/l9WqvyzFGaN62bRg7Q==", - "dev": true, - "requires": { - "@storybook/addons": "6.5.7", - "@storybook/api": "6.5.7", - "@storybook/client-logger": "6.5.7", - "@storybook/components": "6.5.7", - "@storybook/router": "6.5.7", - "@storybook/source-loader": "6.5.7", - "@storybook/theming": "6.5.7", - "core-js": "^3.8.2", - "estraverse": "^5.2.0", - "loader-utils": "^2.0.0", - "prop-types": "^15.7.2", - "react-syntax-highlighter": "^15.4.5", - "regenerator-runtime": "^0.13.7" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "dev": true, - "requires": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - }, - "prismjs": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.28.0.tgz", - "integrity": "sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==", - "dev": true - }, - "react-syntax-highlighter": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", - "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "highlight.js": "^10.4.1", - "lowlight": "^1.17.0", - "prismjs": "^1.27.0", - "refractor": "^3.6.0" - } - }, - "refractor": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", - "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", - "dev": true, - "requires": { - "hastscript": "^6.0.0", - "parse-entities": "^2.0.0", - "prismjs": "~1.27.0" - }, - "dependencies": { - "prismjs": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", - "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", - "dev": true - } - } - } - } - }, "@storybook/addon-toolbars": { "version": "6.5.7", "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-6.5.7.tgz", @@ -19608,7 +19527,7 @@ "app-root-dir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", - "integrity": "sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg=", + "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==", "dev": true }, "app-root-path": { @@ -26253,7 +26172,7 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", "dev": true, "optional": true }, @@ -27890,7 +27809,7 @@ "babel-plugin-add-react-displayname": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz", - "integrity": "sha1-M51M3be2X9YtHfnbn+BN4TQSK9U=", + "integrity": "sha512-LY3+Y0XVDYcShHHorshrDbt4KFWL4bSeniCtl4SYZbask+Syngk1uMPCeN9+nSiZo6zX5s0RTq/J9Pnaaf/KHw==", "dev": true }, "babel-plugin-apply-mdx-type-prop": { @@ -28313,7 +28232,7 @@ "batch-processor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz", - "integrity": "sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg=", + "integrity": "sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==", "dev": true }, "bcrypt-pbkdf": { @@ -29276,7 +29195,7 @@ "camelcase-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "integrity": "sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==", "dev": true, "optional": true, "requires": { @@ -29287,7 +29206,7 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==", "dev": true, "optional": true } @@ -31676,7 +31595,7 @@ "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", "dev": true, "optional": true, "requires": { @@ -36599,7 +36518,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", "dev": true, "optional": true }, @@ -36830,7 +36749,7 @@ "glob-to-regexp": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "integrity": "sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==", "dev": true }, "global": { @@ -37125,7 +37044,7 @@ "has-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-glob/-/has-glob-1.0.0.tgz", - "integrity": "sha1-mqqe7b/7G6OZCnsAEPtnjuAIEgc=", + "integrity": "sha512-D+8A457fBShSEI3tFCj65PAbT++5sKiFtdCdOam0gnfBgw9D277OERk+HM9qYJXmdVLZ/znez10SqHN0BBQ50g==", "dev": true, "requires": { "is-glob": "^3.0.0" @@ -37134,7 +37053,7 @@ "is-glob": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", "dev": true, "requires": { "is-extglob": "^2.1.0" @@ -38999,7 +38918,7 @@ "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", "dev": true, "optional": true }, @@ -39021,7 +38940,7 @@ "is-window": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz", - "integrity": "sha1-LIlspT25feRdPDMTOmXYyfVjSA0=", + "integrity": "sha512-uj00kdXyZb9t9RcAUAwMZAnkBUwdYGhYlt7djMXhfyhUCzwNba50tIiBKR7q0l7tdoBtFVw/3JmLY6fI3rmZmg==", "dev": true }, "is-windows": { @@ -42398,7 +42317,7 @@ "js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=", + "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", "dev": true }, "js-tokens": { @@ -43719,7 +43638,7 @@ "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", "dev": true, "optional": true, "requires": { @@ -43733,7 +43652,7 @@ "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", "dev": true, "optional": true, "requires": { @@ -44041,7 +43960,7 @@ "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", "dev": true, "optional": true, "requires": { @@ -44291,7 +44210,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", "dev": true, "optional": true }, @@ -47331,7 +47250,7 @@ "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==", "dev": true }, "number-is-nan": { @@ -48809,7 +48728,7 @@ "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", "dev": true }, "p-event": { @@ -50383,7 +50302,7 @@ "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", "dev": true }, "prismjs": { @@ -52435,7 +52354,7 @@ "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "integrity": "sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==", "dev": true, "optional": true, "requires": { @@ -52446,7 +52365,7 @@ "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "integrity": "sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==", "dev": true, "optional": true, "requires": { @@ -52689,7 +52608,7 @@ "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", "dev": true }, "remark": { @@ -53227,7 +53146,7 @@ "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", "dev": true, "optional": true, "requires": { @@ -54021,7 +53940,7 @@ "serve-favicon": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.5.0.tgz", - "integrity": "sha1-k10kDN/g9YBTB/3+ln2IlCosvPA=", + "integrity": "sha512-FMW2RvqNr03x+C0WxTyu6sOv21oOjkq5j8tjquWccwa6ScNyGFOGJVpuS1NmTVGBAHS07xnSKotgf2ehQmf9iA==", "dev": true, "requires": { "etag": "~1.8.1", @@ -55362,6 +55281,12 @@ "integrity": "sha512-7t+/wpKLanLzSnQPX8WAcuLCCeuSHoWdQuh9SB3xD0kNOM38DNf+0Oa+wmvxmYueRzkmh6IcdKFtvTa+ecgPDw==", "dev": true }, + "storybook-source-link": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/storybook-source-link/-/storybook-source-link-2.0.3.tgz", + "integrity": "sha512-Vw/ECmTObbcGbS6mX2bolQUF6c/Z4iBtcJBQh6T/3uFDz8pmzGo840hfSJDKXwwatHWDZ1V70jDLCDb4fbXQYg==", + "dev": true + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -56174,7 +56099,7 @@ "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", "dev": true, "optional": true, "requires": { @@ -56195,7 +56120,7 @@ "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "integrity": "sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==", "dev": true, "optional": true, "requires": { @@ -57942,7 +57867,7 @@ "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "integrity": "sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==", "dev": true, "optional": true }, @@ -58455,7 +58380,7 @@ "untildify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz", - "integrity": "sha1-F+soB5h/dpUunASF/DEdBqgmouA=", + "integrity": "sha512-sJjbDp2GodvkB0FZZcn7k6afVisqX5BZD7Yq3xp4nN2O15BBK0cLm3Vwn2vQaF7UDS0UUsrQMkkplmDI5fskig==", "dev": true, "optional": true, "requires": { @@ -58731,7 +58656,7 @@ "utila": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", "dev": true }, "utility-types": { @@ -58752,7 +58677,7 @@ "uuid-browser": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uuid-browser/-/uuid-browser-3.1.0.tgz", - "integrity": "sha1-DwWkCu90+eWVHiDvv0SxGHHlZBA=", + "integrity": "sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg==", "dev": true }, "v8-compile-cache": { @@ -60615,7 +60540,7 @@ "x-default-browser": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/x-default-browser/-/x-default-browser-0.4.0.tgz", - "integrity": "sha1-cM8NqF2nwKtcsPFaiX8jIqa91IE=", + "integrity": "sha512-7LKo7RtWfoFN/rHx1UELv/2zHGMx8MkZKDq1xENmOCTkfIqZJ0zZ26NEJX8czhnPXVcqS0ARjjfJB+eJ0/5Cvw==", "dev": true, "requires": { "default-browser-id": "^1.0.4" diff --git a/package.json b/package.json index 98f8d07607f67c..0168806a72ec37 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,6 @@ "@storybook/addon-controls": "6.5.7", "@storybook/addon-docs": "6.5.7", "@storybook/addon-knobs": "6.2.9", - "@storybook/addon-storysource": "6.5.7", "@storybook/addon-toolbars": "6.5.7", "@storybook/addon-viewport": "6.5.7", "@storybook/builder-webpack5": "6.5.7", @@ -234,6 +233,7 @@ "snapshot-diff": "0.8.1", "source-map-loader": "3.0.0", "sprintf-js": "1.1.1", + "storybook-source-link": "2.0.3", "style-loader": "3.2.1", "terser-webpack-plugin": "5.1.4", "typescript": "4.4.2", diff --git a/storybook/main.js b/storybook/main.js index 99c8c2ea84bec1..717f42f7fb5e6d 100644 --- a/storybook/main.js +++ b/storybook/main.js @@ -19,11 +19,11 @@ module.exports = { }, '@storybook/addon-controls', '@storybook/addon-knobs', // Deprecated, new stories should use addon-controls. - '@storybook/addon-storysource', '@storybook/addon-viewport', '@storybook/addon-a11y', '@storybook/addon-toolbars', '@storybook/addon-actions', + 'storybook-source-link', ], features: { babelModeV7: true, diff --git a/storybook/preview.js b/storybook/preview.js index 16e8f71cf53465..c7c2463f797d74 100644 --- a/storybook/preview.js +++ b/storybook/preview.js @@ -107,4 +107,5 @@ export const parameters = { ], }, }, + sourceLinkPrefix: 'https://github.com/WordPress/gutenberg/blob/trunk/', }; diff --git a/storybook/stories/docs/inline-icon.js b/storybook/stories/docs/inline-icon.js new file mode 100644 index 00000000000000..2a1056b0069b74 --- /dev/null +++ b/storybook/stories/docs/inline-icon.js @@ -0,0 +1,14 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; +import { Icons } from '@storybook/components'; + +const StyledIcons = styled( Icons )` + display: inline-block !important; + width: 14px; +`; + +export const InlineIcon = ( props ) => ( + +); diff --git a/storybook/stories/docs/introduction.story.mdx b/storybook/stories/docs/introduction.story.mdx index bdad5c0336ebfe..f353cf7c7e0abc 100644 --- a/storybook/stories/docs/introduction.story.mdx +++ b/storybook/stories/docs/introduction.story.mdx @@ -1,4 +1,5 @@ import { Meta } from '@storybook/addon-docs'; +import { InlineIcon } from './inline-icon'; @@ -17,24 +18,25 @@ Import them from the components root directory like in below example: import { Button } from '@wordpress/components'; export default function MyButton() { - return ; + return ; } -```` +``` ## How this site works -The site shows the individual components in the sidebar and the Canvas on the right. Select the component you’d like to explore, and you’ll see the display on the Canvas tab. If the component also has controls/arguments, you can modify them on the Controls tab on the lower half of the screen. +The site shows the individual components in the sidebar and the Canvas on the right. Select the component you’d like to explore, and you’ll see the display on the **Canvas** tab. If the component also has controls/arguments, you can modify them on the **Controls** tab on the lower half of the screen. To view the documentation for each component use the **Docs** menu item in the top toolbar. +To view the source code for the component and its stories on GitHub, click the View Source Repository button in the top right corner. + To use it in your local development environment run the following command in the top level Gutenberg directory: - ```bash - npm run storybook:dev - ``` +```bash +npm run storybook:dev +``` ## Resources to learn more: -- [Storybook.js.org](https://storybook.js.org/) - Storybook is a frontend workshop for building UI components and pages in isolation. -- [[Package] Components](https://github.com/WordPress/gutenberg/issues?q=is%3Aopen+is%3Aissue+label%3A%22%5BPackage%5D+Components%22) - Open Issue Gutenberg Repo -- [On the known "loading source..." issue](https://github.com/WordPress/gutenberg/issues/45095) at the 'Story' tab for some components +- [Storybook.js.org](https://storybook.js.org/) - Storybook is a frontend workshop for building UI components and pages in isolation. +- [[Package] Components](https://github.com/WordPress/gutenberg/issues?q=is%3Aopen+is%3Aissue+label%3A%22%5BPackage%5D+Components%22) - Open Issue Gutenberg Repo diff --git a/storybook/stories/playground/index.js b/storybook/stories/playground/index.js index a13001f96f28bc..92ccd78ae58b74 100644 --- a/storybook/stories/playground/index.js +++ b/storybook/stories/playground/index.js @@ -70,6 +70,9 @@ function App() { export default { title: 'Playground/Block Editor', + parameters: { + sourceLink: 'storybook/stories/playground', + }, }; export const _default = () => { diff --git a/storybook/webpack.config.js b/storybook/webpack.config.js index 000c6930574f9b..1747cc2f9caa0a 100644 --- a/storybook/webpack.config.js +++ b/storybook/webpack.config.js @@ -44,6 +44,15 @@ module.exports = ( { config } ) => { ), enforce: 'post', }, + { + // Adds a `sourceLink` parameter to the story metadata, based on the file path + test: /\/stories\/.+\.(j|t)sx?$/, + loader: path.resolve( + __dirname, + './webpack/source-link-loader.js' + ), + enforce: 'post', + }, { test: /\.scss$/, exclude: /\.lazy\.scss$/, diff --git a/storybook/webpack/source-link-loader.js b/storybook/webpack/source-link-loader.js new file mode 100644 index 00000000000000..d9501aa737dad3 --- /dev/null +++ b/storybook/webpack/source-link-loader.js @@ -0,0 +1,67 @@ +/** + * External dependencies + */ +const babel = require( '@babel/core' ); +const path = require( 'path' ); + +const REPO_ROOT = path.resolve( __dirname, '../../' ); + +/** + * Adds a `sourceLink` parameter to the story metadata, based on the file path. + * + * @see https://storybook.js.org/addons/storybook-source-link + */ +function addSourceLinkPlugin() { + return { + visitor: { + ExportDefaultDeclaration( visitorPath, state ) { + const componentPath = getComponentPathFromStoryPath( + state.file.opts.filename + ); + const properties = + // When default export is anonymous, the declaration is an object expression + visitorPath.node.declaration.properties ?? + // When default export is named, the declaration is an identifier, usually the previous node + visitorPath.getPrevSibling().node.declarations[ 0 ].init + .properties; + + alterParameters( properties, componentPath ); + }, + }, + }; +} + +function getComponentPathFromStoryPath( storyPath ) { + const componentRoot = path.resolve( storyPath, '../../' ); + return path.relative( REPO_ROOT, componentRoot ); +} + +function alterParameters( properties, componentPath ) { + const sourceLink = babel.types.objectProperty( + babel.types.identifier( 'sourceLink' ), + babel.types.stringLiteral( componentPath ) + ); + + let parameters = properties.find( ( op ) => op.key.name === 'parameters' ); + + if ( ! parameters ) { + parameters = babel.types.objectProperty( + babel.types.identifier( 'parameters' ) + ); + properties.push( parameters ); + } + + parameters.value.properties = [ + sourceLink, + ...parameters.value.properties, + ]; +} + +module.exports = function ( source, { sources } ) { + const output = babel.transform( source, { + plugins: [ addSourceLinkPlugin ], + filename: sources[ 0 ], + sourceType: 'module', + } ); + return output.code; +}; From a5f2b81fa25983dcd23f13045f2b7f62cdce1b7f Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 18 Nov 2022 20:26:44 +1000 Subject: [PATCH 19/76] Sidebar: Add list view tab for Navigation block et al. (#45483) * Add drawer icons to icons package Switch editor sidebar icons to new drawer icons Icon should reflect RTL as well. Update TabPanel to allow icons on TabButtons Add menu group to InspectorControl slot fills Move nav menu controls into InspectorControls menu group Introduce menu, settings & appearance tabs to sidebar Refactor InspectorControlTabs Re-orders the appearance and settings tabs. Also now omits the TabPanel altogether if only a single tab has contents. Make block inspector tabs a Gutenberg experiment A little tidy up Clean up conditional tabs display Remove nav specific menu tab for now Remove Menu inspector controls group Refactor inspector controls tabs to components Remove conditional display of tabs Render no settings or tools messages when no fills Reduce new styles for equal width tabs Add general slot for items applying to block as a whole Move query block new post link to new slot Revert "Move query block new post link to new slot" This reverts commit 12799852648a67cb566112639d4d71e33a2d9834. Revert "Add general slot for items applying to block as a whole" This reverts commit 186276fb42af1a7dd571892b349eaacd34f0a3b0. Tweak no style options message Add changelog for TabPanel updates Remove empty readme until experiment settles Fix copy and paste error Provide list view tab and slot for nav block * change white to allow Co-authored-by: Ben Dwyer --- .../inspector-controls-tabs/index.js | 19 +++++++- .../inspector-controls-tabs/list-view-tab.js | 44 +++++++++++++++++++ .../inspector-controls-tabs/utils.js | 10 ++++- .../components/inspector-controls/groups.js | 2 + .../edit/menu-inspector-controls.js | 5 ++- packages/components/CHANGELOG.md | 1 + 6 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/list-view-tab.js diff --git a/packages/block-editor/src/components/inspector-controls-tabs/index.js b/packages/block-editor/src/components/inspector-controls-tabs/index.js index 62b87dd58e11cf..ad34f5625b5dd5 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/index.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/index.js @@ -6,17 +6,23 @@ import { TabPanel } from '@wordpress/components'; /** * Internal dependencies */ -import { TAB_SETTINGS, TAB_APPEARANCE } from './utils'; +import { TAB_SETTINGS, TAB_APPEARANCE, TAB_LIST_VIEW } from './utils'; import AppearanceTab from './appearance-tab'; import SettingsTab from './settings-tab'; +import { default as ListViewTab, useIsListViewDisabled } from './list-view-tab'; -const tabs = [ TAB_APPEARANCE, TAB_SETTINGS ]; +const defaultTabs = [ TAB_APPEARANCE, TAB_SETTINGS ]; +const tabsWithListView = [ TAB_LIST_VIEW, TAB_APPEARANCE, TAB_SETTINGS ]; export default function InspectorControlsTabs( { blockName, clientId, hasBlockStyles, } ) { + const tabs = useIsListViewDisabled( blockName ) + ? defaultTabs + : tabsWithListView; + return ( { ( tab ) => { @@ -36,6 +42,15 @@ export default function InspectorControlsTabs( { /> ); } + + if ( tab.name === TAB_LIST_VIEW.name ) { + return ( + + ); + } } } ); diff --git a/packages/block-editor/src/components/inspector-controls-tabs/list-view-tab.js b/packages/block-editor/src/components/inspector-controls-tabs/list-view-tab.js new file mode 100644 index 00000000000000..d95f39b6e96515 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/list-view-tab.js @@ -0,0 +1,44 @@ +/** + * WordPress dependencies + */ +import { __experimentalUseSlotFills as useSlotFills } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import InspectorControls from '../inspector-controls'; +import InspectorControlsGroups from '../inspector-controls/groups'; + +// This tab restricts the blocks that may render to it via the allowlist below. +const allowlist = [ 'core/navigation' ]; + +export const useIsListViewDisabled = ( blockName ) => { + return ! allowlist.includes( blockName ); +}; + +const ListViewTab = ( { blockName, hasSingleBlockSelection } ) => { + const { list } = InspectorControlsGroups; + const fills = useSlotFills( list.Slot.__unstableName ) || []; + + // Unlike other tabs the List View is much more niche. As such it will be + // omitted if the current block isn't in the allowlist. + if ( useIsListViewDisabled( blockName ) ) { + return; + } + + return ( + <> + { ! fills.length && ( + + { hasSingleBlockSelection + ? __( 'This block has no list options.' ) + : __( 'The selected blocks have no list options.' ) } + + ) } + + + ); +}; + +export default ListViewTab; diff --git a/packages/block-editor/src/components/inspector-controls-tabs/utils.js b/packages/block-editor/src/components/inspector-controls-tabs/utils.js index 0bec1088174d31..4ab6f0164a19c0 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/utils.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/utils.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { cog, styles } from '@wordpress/icons'; +import { cog, styles, listView } from '@wordpress/icons'; export const TAB_SETTINGS = { name: 'settings', @@ -18,3 +18,11 @@ export const TAB_APPEARANCE = { icon: styles, className: 'block-editor-block-inspector__tab-item', }; + +export const TAB_LIST_VIEW = { + name: 'list', + title: 'List View', + value: 'list-view', + icon: listView, + className: 'block-editor-block-inspector__tab-item', +}; diff --git a/packages/block-editor/src/components/inspector-controls/groups.js b/packages/block-editor/src/components/inspector-controls/groups.js index 0b7d1d2f4479f9..cb03c1ff13fa57 100644 --- a/packages/block-editor/src/components/inspector-controls/groups.js +++ b/packages/block-editor/src/components/inspector-controls/groups.js @@ -13,6 +13,7 @@ const InspectorControlsDimensions = createSlotFill( const InspectorControlsTypography = createSlotFill( 'InspectorControlsTypography' ); +const InspectorControlsListView = createSlotFill( 'InspectorControlsListView' ); const groups = { default: InspectorControlsDefault, @@ -20,6 +21,7 @@ const groups = { border: InspectorControlsBorder, color: InspectorControlsColor, dimensions: InspectorControlsDimensions, + list: InspectorControlsListView, typography: InspectorControlsTypography, }; diff --git a/packages/block-library/src/navigation/edit/menu-inspector-controls.js b/packages/block-library/src/navigation/edit/menu-inspector-controls.js index 9f92d130ff14d0..330812ef3e981c 100644 --- a/packages/block-library/src/navigation/edit/menu-inspector-controls.js +++ b/packages/block-library/src/navigation/edit/menu-inspector-controls.js @@ -66,9 +66,12 @@ const MenuInspectorControls = ( { } ) => { const isOffCanvasNavigationEditorEnabled = window?.__experimentalEnableOffCanvasNavigationEditor === true; + const menuControlsSlot = window?.__experimentalEnableBlockInspectorTabs + ? 'list' + : undefined; return ( - + Date: Fri, 18 Nov 2022 11:40:04 +0100 Subject: [PATCH 20/76] Fix NavigableRegion README (#45879) --- .../src/components/navigable-region/README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/interface/src/components/navigable-region/README.md b/packages/interface/src/components/navigable-region/README.md index 6f98d17d79c45a..5a931475443b19 100644 --- a/packages/interface/src/components/navigable-region/README.md +++ b/packages/interface/src/components/navigable-region/README.md @@ -4,8 +4,6 @@ It also renders a child `div` element that is responsible to set a negative `z-index` stack level, to make sure the focus style is always visible, regardless of other elements that may cut-off the focus style outline otherwise. -It can use a CSS animation via the `motion` component. - ## Props ### children @@ -29,10 +27,10 @@ A meaningful name for the ARIA landmark region. - Type: `String` - Required: Yes -### motionProps +### as -Properties of `motionProps` object will be used by the `motion` component to set a CSS animation on the wrapper div. +The component used as the root of the region. Defaults to a `div` element. -- Type: `Object` -- Required: No -- Default: `{}` +- Type: `Component` +- Required: no +- Default: `div` From 9e6e8c9cb7e8cf35ea148d28dfb7dc21b1c4b915 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 18 Nov 2022 10:42:03 +0000 Subject: [PATCH 21/76] Nav offcanvas editor: add simple back button to inspector controls (#45852) * Add very simple back button to block inspector * Enable for experiment only * Change button description label --- .../src/components/block-card/index.js | 30 +++++++++++++++++-- .../src/components/block-inspector/index.js | 23 ++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index 21832629b9557f..6362b1b4ca2c7c 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -2,13 +2,22 @@ * WordPress dependencies */ import deprecated from '@wordpress/deprecated'; - +import { Button } from '@wordpress/components'; +import { chevronLeft, chevronRight } from '@wordpress/icons'; +import { __, isRTL } from '@wordpress/i18n'; /** * Internal dependencies */ import BlockIcon from '../block-icon'; -function BlockCard( { title, icon, description, blockType } ) { +function BlockCard( { + title, + icon, + description, + blockType, + parentBlockClientId, + handleBackButton, +} ) { if ( blockType ) { deprecated( '`blockType` property in `BlockCard component`', { since: '5.7', @@ -16,8 +25,25 @@ function BlockCard( { title, icon, description, blockType } ) { } ); ( { title, icon, description } = blockType ); } + + const isOffCanvasNavigationEditorEnabled = + window?.__experimentalEnableOffCanvasNavigationEditor === true; + return (
+ { isOffCanvasNavigationEditorEnabled && parentBlockClientId && ( + ) } renderContent={ () => test } + popoverProps={ { 'data-testid': 'popover' } } /> ); - expectButtonExpanded( dropdownContainer, false ); - expectPopoverVisible( dropdownContainer, false ); + const button = screen.getByRole( 'button', { expanded: false } ); + + expect( button ).toBeVisible(); + expect( screen.queryByTestId( 'popover' ) ).not.toBeInTheDocument(); - const button = getButtonElement( dropdownContainer ); - fireEvent.click( button ); + await user.click( button ); - expectButtonExpanded( dropdownContainer, true ); - expectPopoverVisible( dropdownContainer, true ); + expect( + screen.getByRole( 'button', { expanded: true } ) + ).toBeVisible(); + + await waitFor( () => + expect( screen.getByTestId( 'popover' ) ).toBeVisible() + ); + + // Cleanup remaining effects, like the delayed popover positioning + unmount(); } ); - it( 'should close the dropdown when calling onClose', () => { - const { - container: { firstChild: dropdownContainer }, - } = render( + it( 'should close the dropdown when calling onClose', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + render( { aria-expanded={ isOpen } onClick={ onToggle } > - Toggleee + Toggle , , ] } renderContent={ () => null } + popoverProps={ { 'data-testid': 'popover' } } /> ); - expectPopoverVisible( dropdownContainer, false ); + expect( screen.queryByTestId( 'popover' ) ).not.toBeInTheDocument(); - const openButton = getOpenCloseButton( dropdownContainer, '.open' ); - fireEvent.click( openButton ); + await user.click( screen.getByRole( 'button', { name: 'Toggle' } ) ); - expectPopoverVisible( dropdownContainer, true ); + await waitFor( () => + expect( screen.getByTestId( 'popover' ) ).toBeVisible() + ); - const closeButton = getOpenCloseButton( dropdownContainer, '.close' ); - fireEvent.click( closeButton ); + await user.click( screen.getByRole( 'button', { name: 'close' } ) ); - expectPopoverVisible( dropdownContainer, false ); + expect( screen.queryByTestId( 'popover' ) ).not.toBeInTheDocument(); } ); } ); From b862a97ba27b1b3068330c62938d3f6496f4471c Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Fri, 18 Nov 2022 23:38:39 +0200 Subject: [PATCH 31/76] Element: Fix no-node-access in createInterpolateElement (#45894) --- .../element/src/test/create-interpolate-element.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/element/src/test/create-interpolate-element.js b/packages/element/src/test/create-interpolate-element.js index b8c5d4757e1cb7..e78d3b1c558441 100644 --- a/packages/element/src/test/create-interpolate-element.js +++ b/packages/element/src/test/create-interpolate-element.js @@ -209,15 +209,13 @@ describe( 'createInterpolateElement', () => { }; const { container, rerender } = render( ); - expect( container.firstChild ).toContainHTML( 'string!' ); - expect( container.firstChild ).not.toContainHTML( '' ); + expect( container ).toContainHTML( 'string!' ); + expect( container ).not.toContainHTML( '' ); rerender( ); - expect( container.firstChild ).toContainHTML( - 'string!' - ); - expect( container.firstChild ).not.toContainHTML( '' ); + expect( container ).toContainHTML( 'string!' ); + expect( container ).not.toContainHTML( '' ); } ); it( 'handles parsing emojii correctly', () => { const testString = '👳‍♀️🚨🤷‍♂️⛈️fully here'; From 109aec494a01414b5abc83b1999bd610b8ae21c7 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Fri, 18 Nov 2022 23:38:59 +0200 Subject: [PATCH 32/76] Components: Fix no-node-access in Sandbox tests (#45908) --- packages/components/src/sandbox/test/index.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/components/src/sandbox/test/index.js b/packages/components/src/sandbox/test/index.js index d91dce1842199a..f25bdca675cbfd 100644 --- a/packages/components/src/sandbox/test/index.js +++ b/packages/components/src/sandbox/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, within } from '@testing-library/react'; /** * WordPress dependencies @@ -16,15 +16,15 @@ import Sandbox from '../'; describe( 'Sandbox', () => { const TestWrapper = () => { const [ html, setHtml ] = useState( - // MuatationObserver implementation from JSDom does not work as intended + // MutationObserver implementation from JSDom does not work as intended // with iframes so we need to ignore it for the time being. '' + - '' + '' ); const updateHtml = () => { setHtml( - '' + '' ); }; @@ -33,18 +33,19 @@ describe( 'Sandbox', () => { - +
); }; it( 'should rerender with new emdeded content if html prop changes', () => { - const { container } = render( ); + render( ); - const iframe = container.querySelector( '.components-sandbox' ); + const iframe = screen.getByTitle( 'Sandbox Title' ); - let sandboxedIframe = - iframe.contentWindow.document.body.querySelector( '.mock-iframe' ); + let sandboxedIframe = within( + iframe.contentWindow.document.body + ).getByTitle( 'Mock Iframe' ); expect( sandboxedIframe ).toHaveAttribute( 'src', @@ -53,8 +54,9 @@ describe( 'Sandbox', () => { fireEvent.click( screen.getByRole( 'button' ) ); - sandboxedIframe = - iframe.contentWindow.document.body.querySelector( '.mock-iframe' ); + sandboxedIframe = within( + iframe.contentWindow.document.body + ).getByTitle( 'Mock Iframe' ); expect( sandboxedIframe ).toHaveAttribute( 'src', From 8795c6e336d7cdf69caa02762bb3a0f4681c6fa7 Mon Sep 17 00:00:00 2001 From: Marissa <85708316+marissa-makes@users.noreply.github.com> Date: Sat, 19 Nov 2022 00:01:33 -0500 Subject: [PATCH 33/76] BlockVariationPicker: Remove unnecessary aria role (#45916) --- .../src/components/block-variation-picker/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-variation-picker/index.js b/packages/block-editor/src/components/block-variation-picker/index.js index 24bdfa272c34b2..27e214ec39bb07 100644 --- a/packages/block-editor/src/components/block-variation-picker/index.js +++ b/packages/block-editor/src/components/block-variation-picker/index.js @@ -49,10 +49,7 @@ function BlockVariationPicker( { className="block-editor-block-variation-picker__variation" label={ variation.description || variation.title } /> - + { variation.title } From 9d936e44729d65d1522f1f2eccffd587c205f420 Mon Sep 17 00:00:00 2001 From: Seth Miller Date: Sun, 20 Nov 2022 19:19:35 -0500 Subject: [PATCH 34/76] Added InspectorControls import to example (#45872) * Added InspectorControls import to example Fixes InspectorControls is not defined * Update docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md Co-authored-by: Ryan Welcher --- .../block-tutorial/extending-the-query-loop-block.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md b/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md index 3956971645f86a..e43dcb9727088d 100644 --- a/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md +++ b/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md @@ -192,6 +192,8 @@ Notice that we have also disabled the `postType` control. When the user selects Because our plugin uses custom attributes that we need to query, we want to add our own controls to allow the users to select those instead of the ones we have just disabled from the core inspector controls. We can do this via a [React HOC](https://reactjs.org/docs/higher-order-components.html) hooked into a [block filter](https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/), like so: ```jsx +import { InspectorControls } from '@wordpress/block-editor'; + export const withBookQueryControls = ( BlockEdit ) => ( props ) => { // We only want to add these controls if it is our variation, // so here we can implement a custom logic to check for that, similiar From 5046795e516f6cb02b9382c3354ce48dea3bda73 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Sat, 19 Nov 2022 18:52:51 -0700 Subject: [PATCH 35/76] Perf Tests: Fix test build process to call wp-scripts In #45284 we started skipping the generate-types phase of the build when running the performance tests. This was done to avoid the time it takes to generate those types when they are ignored by the rest of the test suite. In that patch we accidentally skipped running `wp-scripts build`, and in this patch we're bringing that back to fix the mistake. --- bin/plugin/commands/performance.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index a7f9c81e8cebb3..31a537f373731d 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -276,7 +276,7 @@ async function runPerformanceTests( branches, options ) { log( ` >> Building the ${ fancyBranch } branch` ); await runShellScript( - 'npm ci && node ./bin/packages/build.js', + 'npm ci && npm run prebuild:packages && node ./bin/packages/build.js && npx wp-scripts build', buildPath ); } From 35f25ab846f30eccbbf48ea6911e88f35f030c5b Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Sun, 20 Nov 2022 22:00:48 -0700 Subject: [PATCH 36/76] Add submenu menu item to list view (#45794) --- .../src/components/off-canvas-editor/block.js | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/off-canvas-editor/block.js b/packages/block-editor/src/components/off-canvas-editor/block.js index 50119ebddfb579..a0bf8529a4d01c 100644 --- a/packages/block-editor/src/components/off-canvas-editor/block.js +++ b/packages/block-editor/src/components/off-canvas-editor/block.js @@ -6,10 +6,11 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { hasBlockSupport } from '@wordpress/blocks'; +import { createBlock, hasBlockSupport } from '@wordpress/blocks'; import { __experimentalTreeGridCell as TreeGridCell, __experimentalTreeGridItem as TreeGridItem, + MenuItem, } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { moreVertical } from '@wordpress/icons'; @@ -59,7 +60,7 @@ function ListViewBlock( { } ) { const cellRef = useRef( null ); const [ isHovered, setIsHovered ] = useState( false ); - const { clientId } = block; + const { clientId, attributes } = block; const { isLocked, isContentLocked } = useBlockLock( clientId ); const forceSelectionContentLock = useSelect( @@ -86,7 +87,8 @@ function ListViewBlock( { ( isSelected && selectedClientIds[ selectedClientIds.length - 1 ] === clientId ); - const { toggleBlockHighlight } = useDispatch( blockEditorStore ); + const { replaceBlock, toggleBlockHighlight } = + useDispatch( blockEditorStore ); const blockInformation = useBlockDisplayInformation( clientId ); const blockName = useSelect( @@ -355,7 +357,34 @@ function ListViewBlock( { } } disableOpenOnArrowDown __experimentalSelectBlock={ updateSelection } - /> + > + { ( { onClose } ) => ( + { + const newLink = createBlock( + 'core/navigation-link' + ); + const newSubmenu = createBlock( + 'core/navigation-submenu', + attributes, + block.innerBlocks + ? [ + ...block.innerBlocks, + newLink, + ] + : [ newLink ] + ); + replaceBlock( + clientId, + newSubmenu + ); + onClose(); + } } + > + { __( 'Add a submenu item' ) } + + ) } + ) } From 1b69ba2b9b42fe1b6fee765d06c401dd1ac710c6 Mon Sep 17 00:00:00 2001 From: Mitchell Austin Date: Sun, 20 Nov 2022 23:02:25 -0800 Subject: [PATCH 37/76] Storybook: Opt in to story store v7 (#42486) * Opt in to code-split stories * Revert glob * Move story utility files to subfolders Co-authored-by: Lena Morita --- .../src/components/inserter/stories/index.js | 2 +- .../inserter/stories/{ => utils}/fixtures.js | 0 .../src/navigation/stories/index.js | 12 ++++----- .../stories/{ => utils}/controlled-state.js | 6 ++--- .../navigation/stories/{ => utils}/default.js | 6 ++--- .../navigation/stories/{ => utils}/group.js | 8 +++--- .../stories/{ => utils}/hide-if-empty.js | 6 ++--- .../stories/{ => utils}/more-examples.js | 8 +++--- .../navigation/stories/{ => utils}/search.js | 10 ++++---- .../src/tools-panel/stories/index.js | 2 +- .../tools-panel-with-item-group-slot.js | 25 +++++++++++-------- packages/components/tsconfig.json | 4 +-- storybook/main.js | 2 ++ 13 files changed, 48 insertions(+), 43 deletions(-) rename packages/block-editor/src/components/inserter/stories/{ => utils}/fixtures.js (100%) rename packages/components/src/navigation/stories/{ => utils}/controlled-state.js (96%) rename packages/components/src/navigation/stories/{ => utils}/default.js (93%) rename packages/components/src/navigation/stories/{ => utils}/group.js (86%) rename packages/components/src/navigation/stories/{ => utils}/hide-if-empty.js (90%) rename packages/components/src/navigation/stories/{ => utils}/more-examples.js (96%) rename packages/components/src/navigation/stories/{ => utils}/search.js (89%) rename packages/components/src/tools-panel/stories/{ => utils}/tools-panel-with-item-group-slot.js (92%) diff --git a/packages/block-editor/src/components/inserter/stories/index.js b/packages/block-editor/src/components/inserter/stories/index.js index c74ca5fe79ee1b..f6949653c87872 100644 --- a/packages/block-editor/src/components/inserter/stories/index.js +++ b/packages/block-editor/src/components/inserter/stories/index.js @@ -3,7 +3,7 @@ */ import BlockLibrary from '../library'; import BlockEditorProvider from '../../provider'; -import { patternCategories, patterns, reusableBlocks } from './fixtures'; +import { patternCategories, patterns, reusableBlocks } from './utils/fixtures'; import Inserter from '../'; export default { title: 'BlockEditor/Inserter' }; diff --git a/packages/block-editor/src/components/inserter/stories/fixtures.js b/packages/block-editor/src/components/inserter/stories/utils/fixtures.js similarity index 100% rename from packages/block-editor/src/components/inserter/stories/fixtures.js rename to packages/block-editor/src/components/inserter/stories/utils/fixtures.js diff --git a/packages/components/src/navigation/stories/index.js b/packages/components/src/navigation/stories/index.js index 0c28f8c58347d2..8fbdde44fe7dff 100644 --- a/packages/components/src/navigation/stories/index.js +++ b/packages/components/src/navigation/stories/index.js @@ -6,12 +6,12 @@ import NavigationBackButton from '../back-button'; import NavigationGroup from '../group'; import NavigationItem from '../item'; import NavigationMenu from '../menu'; -import { DefaultStory } from './default'; -import { GroupStory } from './group'; -import { ControlledStateStory } from './controlled-state'; -import { SearchStory } from './search'; -import { MoreExamplesStory } from './more-examples'; -import { HideIfEmptyStory } from './hide-if-empty'; +import { DefaultStory } from './utils/default'; +import { GroupStory } from './utils/group'; +import { ControlledStateStory } from './utils/controlled-state'; +import { SearchStory } from './utils/search'; +import { MoreExamplesStory } from './utils/more-examples'; +import { HideIfEmptyStory } from './utils/hide-if-empty'; import './style.css'; export default { diff --git a/packages/components/src/navigation/stories/controlled-state.js b/packages/components/src/navigation/stories/utils/controlled-state.js similarity index 96% rename from packages/components/src/navigation/stories/controlled-state.js rename to packages/components/src/navigation/stories/utils/controlled-state.js index 68c1be54f1bffb..ed7d652d0df778 100644 --- a/packages/components/src/navigation/stories/controlled-state.js +++ b/packages/components/src/navigation/stories/utils/controlled-state.js @@ -7,9 +7,9 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; +import Navigation from '../..'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; export function ControlledStateStory() { const [ activeItem, setActiveItem ] = useState( 'item-1' ); diff --git a/packages/components/src/navigation/stories/default.js b/packages/components/src/navigation/stories/utils/default.js similarity index 93% rename from packages/components/src/navigation/stories/default.js rename to packages/components/src/navigation/stories/utils/default.js index cbcfe53338811d..569cb8aa17b4bc 100644 --- a/packages/components/src/navigation/stories/default.js +++ b/packages/components/src/navigation/stories/utils/default.js @@ -6,9 +6,9 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; +import Navigation from '../..'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; export function DefaultStory() { const [ activeItem, setActiveItem ] = useState( 'item-1' ); diff --git a/packages/components/src/navigation/stories/group.js b/packages/components/src/navigation/stories/utils/group.js similarity index 86% rename from packages/components/src/navigation/stories/group.js rename to packages/components/src/navigation/stories/utils/group.js index 1c919d50464f23..7e11071a63b9a4 100644 --- a/packages/components/src/navigation/stories/group.js +++ b/packages/components/src/navigation/stories/utils/group.js @@ -6,10 +6,10 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; -import NavigationGroup from '../group'; +import Navigation from '../..'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; +import NavigationGroup from '../../group'; export function GroupStory() { const [ activeItem, setActiveItem ] = useState( 'item-1' ); diff --git a/packages/components/src/navigation/stories/hide-if-empty.js b/packages/components/src/navigation/stories/utils/hide-if-empty.js similarity index 90% rename from packages/components/src/navigation/stories/hide-if-empty.js rename to packages/components/src/navigation/stories/utils/hide-if-empty.js index 0977a41ab18463..ef9e889932a895 100644 --- a/packages/components/src/navigation/stories/hide-if-empty.js +++ b/packages/components/src/navigation/stories/utils/hide-if-empty.js @@ -1,9 +1,9 @@ /** * Internal dependencies */ -import Navigation from '..'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; +import Navigation from '../..'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; export function HideIfEmptyStory() { return ( diff --git a/packages/components/src/navigation/stories/more-examples.js b/packages/components/src/navigation/stories/utils/more-examples.js similarity index 96% rename from packages/components/src/navigation/stories/more-examples.js rename to packages/components/src/navigation/stories/utils/more-examples.js index 2887ab1241c0c6..43bd33835db370 100644 --- a/packages/components/src/navigation/stories/more-examples.js +++ b/packages/components/src/navigation/stories/utils/more-examples.js @@ -7,10 +7,10 @@ import { Icon, wordpress, home } from '@wordpress/icons'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationGroup from '../group'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; +import Navigation from '../..'; +import NavigationGroup from '../../group'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; export function MoreExamplesStory() { const [ activeItem, setActiveItem ] = useState( 'child-1' ); diff --git a/packages/components/src/navigation/stories/search.js b/packages/components/src/navigation/stories/utils/search.js similarity index 89% rename from packages/components/src/navigation/stories/search.js rename to packages/components/src/navigation/stories/utils/search.js index d9bcd405cbc349..6fb1a176970233 100644 --- a/packages/components/src/navigation/stories/search.js +++ b/packages/components/src/navigation/stories/utils/search.js @@ -6,11 +6,11 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationGroup from '../group'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; -import { normalizedSearch } from '../utils'; +import Navigation from '../..'; +import NavigationGroup from '../../group'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; +import { normalizedSearch } from '../../utils'; const searchItems = [ { item: 'foo', title: 'Foo' }, diff --git a/packages/components/src/tools-panel/stories/index.js b/packages/components/src/tools-panel/stories/index.js index 9f03ebbede6372..49f9d366587cb8 100644 --- a/packages/components/src/tools-panel/stories/index.js +++ b/packages/components/src/tools-panel/stories/index.js @@ -494,7 +494,7 @@ export const WithConditionallyRenderedControl = () => { ); }; -export { ToolsPanelWithItemGroupSlot } from './tools-panel-with-item-group-slot'; +export { ToolsPanelWithItemGroupSlot } from './utils/tools-panel-with-item-group-slot'; const PanelWrapperView = styled.div` font-size: 13px; diff --git a/packages/components/src/tools-panel/stories/tools-panel-with-item-group-slot.js b/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.js similarity index 92% rename from packages/components/src/tools-panel/stories/tools-panel-with-item-group-slot.js rename to packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.js index 717fd3a4485275..d1895324df4c3b 100644 --- a/packages/components/src/tools-panel/stories/tools-panel-with-item-group-slot.js +++ b/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.js @@ -12,17 +12,20 @@ import { useContext, useState } from '@wordpress/element'; /** * Internal dependencies */ -import Button from '../../button'; -import ColorIndicator from '../../color-indicator'; -import ColorPalette from '../../color-palette'; -import Dropdown from '../../dropdown'; -import Panel from '../../panel'; -import { FlexItem } from '../../flex'; -import { HStack } from '../../h-stack'; -import { Item, ItemGroup } from '../../item-group'; -import { ToolsPanel, ToolsPanelItem, ToolsPanelContext } from '..'; -import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; -import { useCx } from '../../utils'; +import Button from '../../../button'; +import ColorIndicator from '../../../color-indicator'; +import ColorPalette from '../../../color-palette'; +import Dropdown from '../../../dropdown'; +import Panel from '../../../panel'; +import { FlexItem } from '../../../flex'; +import { HStack } from '../../../h-stack'; +import { Item, ItemGroup } from '../../../item-group'; +import { ToolsPanel, ToolsPanelItem, ToolsPanelContext } from '../..'; +import { + createSlotFill, + Provider as SlotFillProvider, +} from '../../../slot-fill'; +import { useCx } from '../../../utils'; // Available border colors. const colors = [ diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 107f0ffe586417..04a8eaa8d4d9ec 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -37,8 +37,8 @@ "src/**/*.ios.js", "src/**/*.native.js", "src/**/react-native-*", - "src/**/stories/**.js", // only exclude js files, tsx files should be checked - "src/**/test/**.js", // only exclude js files, ts{x} files should be checked + "src/**/stories/**/*.js", // only exclude js files, tsx files should be checked + "src/**/test/**/*.js", // only exclude js files, ts{x} files should be checked "src/index.js", "src/alignment-matrix-control", "src/angle-picker-control", diff --git a/storybook/main.js b/storybook/main.js index 717f42f7fb5e6d..29880d64557001 100644 --- a/storybook/main.js +++ b/storybook/main.js @@ -25,9 +25,11 @@ module.exports = { '@storybook/addon-actions', 'storybook-source-link', ], + framework: '@storybook/react', features: { babelModeV7: true, emotionAlias: false, + storyStoreV7: true, }, // Workaround: // https://github.com/storybookjs/storybook/issues/12270 From 5c445c240f2aeb5823fe6ea3a97c24aadf85aee3 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 21 Nov 2022 18:56:21 +1100 Subject: [PATCH 38/76] Style Engine: add first draft of contributing doc (#45930) * Initial commit. * Adding `@group` to style engine tests so they can be run altogether Finishing up first draft of CONTRIBUTING.md notes * Formattings * Apply suggestions from code review Co-authored-by: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Co-authored-by: Andrew Serong <14988353+andrewserong@users.noreply.github.com> --- packages/style-engine/CONTRIBUTING.md | 40 +++++++++++++++++++ packages/style-engine/README.md | 2 + ...-wp-style-engine-css-declarations-test.php | 1 + .../class-wp-style-engine-css-rule-test.php | 1 + ...s-wp-style-engine-css-rules-store-test.php | 1 + .../class-wp-style-engine-processor-test.php | 1 + phpunit/style-engine/style-engine-test.php | 2 + 7 files changed, 48 insertions(+) create mode 100644 packages/style-engine/CONTRIBUTING.md diff --git a/packages/style-engine/CONTRIBUTING.md b/packages/style-engine/CONTRIBUTING.md new file mode 100644 index 00000000000000..70bfddb8eb5011 --- /dev/null +++ b/packages/style-engine/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Contributing + +This document contains information you might need to know when extending or debugging Style Engine code. + +## Workflow and build tooling + +The Style Engine PHP and Javascript (JS) files exist inside the `style-engine` package. + +In order to use the Style Engine in the Block Editor, these files must be compiled (in the case of JS) and copied to the build folder. + +When running the `npm run dev` script for example, webpack watches out for changes and will recompile/copy files as necessary if any changes are detected according to the rules in the [packages webpack config](https://github.com/WordPress/gutenberg/tree/HEAD/tools/webpack/packages.js). + +No other configuration is required for JS: webpack will compile and export the Style Engine code as it does with all dependencies listed in [package.json](https://github.com/WordPress/gutenberg/tree/HEAD/package.json). + +The PHP files for packages, however, have a couple of extra steps during the build process: + +1. Functions with the `wp_` prefix are replaced with `gutenberg_`. So, for example, `wp_some_function` becomes `gutenberg_some_function` in the build directory. The reason for this is so that the Block Editor can call Style Engine functions that may have evolved since, or have not yet been included in, any WordPress release. +2. For the same reasons, classes are given a `_Gutenberg` suffix: `WP_Style_Engine` becomes `WP_Style_Engine_Gutenberg`. The [packages webpack config](https://github.com/WordPress/gutenberg/tree/HEAD/tools/webpack/packages.js) contains a static list of PHP classes (`bundledPackagesPhpConfig`) that have to be copied and renamed during build. If you create a new PHP class in the Style Engine package, you should add your class name to the `replaceClasses` array. + +Remember: all PHP functions and methods inside the Style Engine package should use `wp_/WP_` prefixes. Usage outside of the package in Gutenberg can reference the `gutenberg` prefixes or suffixes from the built files. + +When updating existing PHP functions or methods, it's important to check the Block Editor codebase for calls to the equivalent `wp_` functions or classes as they may have to be updated to refer to `gutenberg_` or `_Gutenberg` in order for the updates to take effect. + +## Testing + +[JS unit tests](https://github.com/WordPress/gutenberg/tree/HEAD/packages/style-engine/src/test) are stored next to the source code in the `style-engine` package directory. + +To start the JS unit tests, run: + +`npm run test:unit packages/style-engine/src/test/` + +[PHP unit tests](https://github.com/WordPress/gutenberg/tree/HEAD/phpunit/style-engine) are located in the root `phpunit` directory. + +In order to test the latest version of the Style Engine and avoid conflicts with existing Core equivalents, all PHP unit tests call the `gutenberg_` functions and `_Gutenberg` classes. + +Therefore, Style Engine PHP source files should be parsed and copied to the build folder before running tests. During development, this will happen as part of the `npm run dev` script. You can also trigger a build by executing `npm run build`. + +To start the PHP unit tests, run: + +`npm run test:unit:php -- --group style-engine` diff --git a/packages/style-engine/README.md b/packages/style-engine/README.md index 317e48e0c37c48..999fab2aa835e9 100644 --- a/packages/style-engine/README.md +++ b/packages/style-engine/README.md @@ -19,6 +19,8 @@ Upcoming tasks on the roadmap include, but are not limited to, the following: For more information about the roadmap, please refer to [Block editor styles: initiatives and goals](https://make.wordpress.org/core/2022/06/24/block-editor-styles-initiatives-and-goals/) and the [Github project board](https://github.com/orgs/WordPress/projects/19). +If you're making changes or additions to the Style Engine, please take a moment to read the [notes on contributing](https://github.com/WordPress/gutenberg/tree/HEAD/packages/style-engine/CONTRIBUTING.md). + ## Backend API ### wp_style_engine_get_styles() diff --git a/phpunit/style-engine/class-wp-style-engine-css-declarations-test.php b/phpunit/style-engine/class-wp-style-engine-css-declarations-test.php index 5380cc81d58314..011de140d08927 100644 --- a/phpunit/style-engine/class-wp-style-engine-css-declarations-test.php +++ b/phpunit/style-engine/class-wp-style-engine-css-declarations-test.php @@ -9,6 +9,7 @@ /** * Tests registering, storing and generating CSS declarations. * + * @group style-engine * @coversDefaultClass WP_Style_Engine_CSS_Declarations_Gutenberg */ class WP_Style_Engine_CSS_Declarations_Test extends WP_UnitTestCase { diff --git a/phpunit/style-engine/class-wp-style-engine-css-rule-test.php b/phpunit/style-engine/class-wp-style-engine-css-rule-test.php index 26ea41c7ce830f..02482c70e4d84a 100644 --- a/phpunit/style-engine/class-wp-style-engine-css-rule-test.php +++ b/phpunit/style-engine/class-wp-style-engine-css-rule-test.php @@ -9,6 +9,7 @@ /** * Tests for registering, storing and generating CSS rules. * + * @group style-engine * @coversDefaultClass WP_Style_Engine_CSS_Rule_Gutenberg */ class WP_Style_Engine_CSS_Rule_Test extends WP_UnitTestCase { diff --git a/phpunit/style-engine/class-wp-style-engine-css-rules-store-test.php b/phpunit/style-engine/class-wp-style-engine-css-rules-store-test.php index 700f63c556c255..8529bff78e22c8 100644 --- a/phpunit/style-engine/class-wp-style-engine-css-rules-store-test.php +++ b/phpunit/style-engine/class-wp-style-engine-css-rules-store-test.php @@ -9,6 +9,7 @@ /** * Tests for registering, storing and retrieving a collection of CSS Rules (a store). * + * @group style-engine * @coversDefaultClass WP_Style_Engine_CSS_Rules_Store_Gutenberg */ class WP_Style_Engine_CSS_Rules_Store_Test extends WP_UnitTestCase { diff --git a/phpunit/style-engine/class-wp-style-engine-processor-test.php b/phpunit/style-engine/class-wp-style-engine-processor-test.php index cfbde08704bdcb..18392f5156fcc5 100644 --- a/phpunit/style-engine/class-wp-style-engine-processor-test.php +++ b/phpunit/style-engine/class-wp-style-engine-processor-test.php @@ -9,6 +9,7 @@ /** * Tests for compiling and rendering styles from a store of CSS rules. * + * @group style-engine * @coversDefaultClass WP_Style_Engine_Processor_Gutenberg */ class WP_Style_Engine_Processor_Test extends WP_UnitTestCase { diff --git a/phpunit/style-engine/style-engine-test.php b/phpunit/style-engine/style-engine-test.php index 5f4c57453e8a8f..66d8dd62865272 100644 --- a/phpunit/style-engine/style-engine-test.php +++ b/phpunit/style-engine/style-engine-test.php @@ -8,6 +8,8 @@ /** * Tests for registering, storing and generating styles. + * + * @group style-engine */ class WP_Style_Engine_Test extends WP_UnitTestCase { /** From 8f57b443358aa8b098589f20ee7d0109bd592db2 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 21 Nov 2022 13:13:29 +0400 Subject: [PATCH 39/76] Site Editor: Fix template list width (#45888) --- packages/edit-site/src/components/list/style.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/list/style.scss b/packages/edit-site/src/components/list/style.scss index e6cf7220cdf604..f18e90c00fc618 100644 --- a/packages/edit-site/src/components/list/style.scss +++ b/packages/edit-site/src/components/list/style.scss @@ -44,9 +44,12 @@ .interface-interface-skeleton__content { background: $white; - align-items: center; padding: $grid-unit-20; + .interface-navigable-region__stacker { + align-items: center; + } + @include break-medium() { padding: $grid-unit * 9; } From abc8d7e850e0e6162725fe22f1b1d033f3d22ee4 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 21 Nov 2022 11:19:31 +0100 Subject: [PATCH 40/76] Cleanup the BlockPreview component (#45936) --- packages/base-styles/_z-index.scss | 1 - .../src/components/block-preview/README.md | 14 -------------- .../src/components/block-preview/index.js | 17 +++++------------ .../src/components/block-preview/live.js | 19 ------------------- .../src/components/block-preview/style.scss | 2 -- 5 files changed, 5 insertions(+), 48 deletions(-) delete mode 100644 packages/block-editor/src/components/block-preview/live.js diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 14305a93611977..9c7b55e78a69cc 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -7,7 +7,6 @@ $z-layers: ( ".block-editor-block-switcher__arrow": 1, ".block-editor-block-list__block {core/image aligned wide or fullwide}": 20, ".block-library-classic__toolbar": 31, // When scrolled to top this toolbar needs to sit over block-editor-block-toolbar - ".block-editor-block-list__layout .reusable-block-indicator": 1, ".block-editor-block-list__block-selection-button": 22, ".components-form-toggle__input": 1, ".edit-post-text-editor__toolbar": 1, diff --git a/packages/block-editor/src/components/block-preview/README.md b/packages/block-editor/src/components/block-preview/README.md index 6df2cb6773878b..0da7348817cd85 100644 --- a/packages/block-editor/src/components/block-preview/README.md +++ b/packages/block-editor/src/components/block-preview/README.md @@ -34,17 +34,3 @@ Width of the preview container in pixels. Controls at what size the blocks will - **Default** `undefined` Padding for the preview container body. - -### `__experimentalLive` - -- **Type** `Boolean` -- **Default:** `false` - -Enables displaying previews without an iframe container. - -### `__experimentalOnClick` - -- **Type** `Function` -- **Default:** `undefined` - -Use this callback in combination with `__experimentalLive`. The callback is attached to the preview container element. diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index b01411591f098d..535a67438cad8e 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -14,7 +14,6 @@ import { memo, useMemo } from '@wordpress/element'; * Internal dependencies */ import BlockEditorProvider from '../provider'; -import LiveBlockPreview from './live'; import AutoHeightBlockPreview from './auto'; import { store as blockEditorStore } from '../../store'; import { BlockListItems } from '../block-list'; @@ -23,8 +22,6 @@ export function BlockPreview( { blocks, __experimentalPadding = 0, viewportWidth = 1200, - __experimentalLive = false, - __experimentalOnClick, __experimentalMinHeight, } ) { const originalSettings = useSelect( @@ -44,15 +41,11 @@ export function BlockPreview( { } return ( - { __experimentalLive ? ( - - ) : ( - - ) } + ); } diff --git a/packages/block-editor/src/components/block-preview/live.js b/packages/block-editor/src/components/block-preview/live.js deleted file mode 100644 index 5015c778defe78..00000000000000 --- a/packages/block-editor/src/components/block-preview/live.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Internal dependencies - */ -import BlockList from '../block-list'; - -export default function LiveBlockPreview( { onClick } ) { - return ( -
-
- -
-
- ); -} diff --git a/packages/block-editor/src/components/block-preview/style.scss b/packages/block-editor/src/components/block-preview/style.scss index 9736d6f6784c46..54dad77698d940 100644 --- a/packages/block-editor/src/components/block-preview/style.scss +++ b/packages/block-editor/src/components/block-preview/style.scss @@ -29,8 +29,6 @@ min-height: auto; .block-editor-block-list__insertion-point, - .block-editor-block-drop-zone, - .reusable-block-indicator, .block-list-appender { display: none; } From 1906e3923ef138cfd5e6a3984981a425c3d00690 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 21 Nov 2022 12:38:16 +0200 Subject: [PATCH 41/76] Block Editor: Fix block alignment tests for React 18 (#45937) --- .../src/components/alignment-control/test/index.js | 5 ++++- .../src/components/block-alignment-control/test/index.js | 5 ++++- .../block-vertical-alignment-control/test/index.js | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/alignment-control/test/index.js b/packages/block-editor/src/components/alignment-control/test/index.js index 14f0d9a04e033c..c792b3e9b7f82b 100644 --- a/packages/block-editor/src/components/alignment-control/test/index.js +++ b/packages/block-editor/src/components/alignment-control/test/index.js @@ -52,7 +52,7 @@ describe( 'AlignmentUI', () => { advanceTimers: jest.advanceTimersByTime, } ); - render( + const { unmount } = render( { name: /^Align text \w+$/, } ) ).toHaveLength( 3 ); + + // Cancel running effects, like delayed dropdown menu popover positioning. + unmount(); } ); test( 'should call on change with undefined when a control is already active', async () => { diff --git a/packages/block-editor/src/components/block-alignment-control/test/index.js b/packages/block-editor/src/components/block-alignment-control/test/index.js index fd9f0f782e25d2..abf2a0a7c448ae 100644 --- a/packages/block-editor/src/components/block-alignment-control/test/index.js +++ b/packages/block-editor/src/components/block-alignment-control/test/index.js @@ -47,7 +47,7 @@ describe( 'BlockAlignmentUI', () => { advanceTimers: jest.advanceTimersByTime, } ); - render( + const { unmount } = render( { name: /^Align \w+$/, } ) ).toHaveLength( 3 ); + + // Cancel running effects, like delayed dropdown menu popover positioning. + unmount(); } ); test( 'should call onChange with undefined, when the control is already active', async () => { diff --git a/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js b/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js index 2e54ee90268549..f2645357a197c3 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js +++ b/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js @@ -45,7 +45,7 @@ describe( 'BlockVerticalAlignmentUI', () => { advanceTimers: jest.advanceTimersByTime, } ); - render( + const { unmount } = render( { name: /^Align \w+$/, } ) ).toHaveLength( 3 ); + + // Cancel running effects, like delayed dropdown menu popover positioning. + unmount(); } ); it( 'should call onChange with undefined, when the control is already active', async () => { From bbc5489c991e2030f9da042683c1b172bfd85215 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 21 Nov 2022 13:14:47 +0200 Subject: [PATCH 42/76] Block Editor: Wait for popover positioning in `MediaReplaceFlow` tests (#45863) * Block Editor: Wait for popover positioning in MediaReplaceFlow tests * Remove an extra backtick * Use Element.closest() to locate closest popover * Fix comment * Remove an excess assertion * Reorder assertions * Introduce a less-specific way to assert positioning * Use the right element when locating the popover * Clarify element.style usage * Wait for the menu to get visible --- .../media-replace-flow/test/index.js | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/media-replace-flow/test/index.js b/packages/block-editor/src/components/media-replace-flow/test/index.js index 8d6bc6ca433782..d73da6c77c5d55 100644 --- a/packages/block-editor/src/components/media-replace-flow/test/index.js +++ b/packages/block-editor/src/components/media-replace-flow/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; /** @@ -32,6 +32,34 @@ function TestWrapper() { ); } +/** + * Returns the first found popover element up the DOM tree. + * + * @param {HTMLElement} element Element to start with. + * @return {HTMLElement|null} Popover element, or `null` if not found. + */ +function getWrappingPopoverElement( element ) { + return element.closest( '.components-popover' ); +} + +/** + * Asserts that the specified popover has already been positioned. + * Necessary because it will be positioned a bit later after it's displayed. + * + * We're intentionally not using `.toHaveStyle()` because we want to be + * less specific and avoid specific values for better test flexibility. + * + * @async + * + * @param {HTMLElement} popover Popover element. + */ +async function popoverIsPositioned( popover ) { + /* eslint-disable jest-dom/prefer-to-have-style */ + await waitFor( () => expect( popover.style.top ).not.toBe( '' ) ); + await waitFor( () => expect( popover.style.left ).not.toBe( '' ) ); + /* eslint-enable jest-dom/prefer-to-have-style */ +} + describe( 'General media replace flow', () => { it( 'renders successfully', () => { render( ); @@ -57,11 +85,11 @@ describe( 'General media replace flow', () => { name: 'Replace', } ) ); - const uploadMenu = screen.getByRole( 'menu' ); - expect( uploadMenu ).toBeInTheDocument(); - expect( uploadMenu ).not.toBeVisible(); + await popoverIsPositioned( getWrappingPopoverElement( uploadMenu ) ); + + await waitFor( () => expect( uploadMenu ).toBeVisible() ); } ); it( 'displays media URL', async () => { @@ -78,11 +106,13 @@ describe( 'General media replace flow', () => { } ) ); - expect( - screen.getByRole( 'link', { - name: 'example.media (opens in a new tab)', - } ) - ).toHaveAttribute( 'href', 'https://example.media' ); + const link = screen.getByRole( 'link', { + name: 'example.media (opens in a new tab)', + } ); + + await popoverIsPositioned( getWrappingPopoverElement( link ) ); + + expect( link ).toHaveAttribute( 'href', 'https://example.media' ); } ); it( 'edits media URL', async () => { @@ -99,6 +129,14 @@ describe( 'General media replace flow', () => { } ) ); + await popoverIsPositioned( + getWrappingPopoverElement( + screen.getByRole( 'link', { + name: 'example.media (opens in a new tab)', + } ) + ) + ); + await user.click( screen.getByRole( 'button', { name: 'Edit', From b55bd2335d32de45e4a7de63eff19ee1070fbce5 Mon Sep 17 00:00:00 2001 From: Miguel Torres Date: Mon, 21 Nov 2022 12:31:32 +0100 Subject: [PATCH 43/76] Ignore cached `wp_theme_has_theme_json` when `WP_DEBUG` is enabled (#45882) --- .../wordpress-6.2/get-global-styles-and-settings.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index b2637969fd8ef6..be9781556868c0 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -20,13 +20,17 @@ function wp_theme_has_theme_json() { $theme_has_support = wp_cache_get( $cache_key, $cache_group ); /** - * $theme_has_support is stored as a int in the cache. + * $theme_has_support is stored as an int in the cache. * * The reason not to store it as a boolean is to avoid working * with the $found parameter which apparently had some issues in some implementations * https://developer.wordpress.org/reference/functions/wp_cache_get/ */ - if ( 0 === $theme_has_support || 1 === $theme_has_support ) { + if ( + // Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. + ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && + ( 0 === $theme_has_support || 1 === $theme_has_support ) + ) { return (bool) $theme_has_support; } From 7ef776b97e760b165a34493412b260fa8a23fd41 Mon Sep 17 00:00:00 2001 From: Vijayan Date: Mon, 21 Nov 2022 18:06:17 +0530 Subject: [PATCH 44/76] Fix link & code markdown (#45708) - Fix Modal component link - Fix code markdown at `Unsupported: Multiple instances` --- packages/components/src/confirm-dialog/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/confirm-dialog/README.md b/packages/components/src/confirm-dialog/README.md index 2f6a764f7f8415..5ed36534898490 100644 --- a/packages/components/src/confirm-dialog/README.md +++ b/packages/components/src/confirm-dialog/README.md @@ -4,7 +4,7 @@ This feature is still experimental. "Experimental" means this is an early implementation subject to drastic and breaking changes.
-`ConfirmDialog` is built of top of [`Modal`](/packages/components/src/modal/README.md] and displays a confirmation dialog, with _confirm_ and _cancel_ buttons. +`ConfirmDialog` is built of top of [`Modal`](/packages/components/src/modal/README.md) and displays a confirmation dialog, with _confirm_ and _cancel_ buttons. The dialog is confirmed by clicking the _confirm_ button or by pressing the `Enter` key. It is cancelled (closed) by clicking the _cancel_ button, by pressing the `ESC` key, or by clicking outside the dialog focus (i.e, the overlay). @@ -72,7 +72,7 @@ function Example() { ### Unsupported: Multiple instances -Multiple `ConfirmDialog's is an edge case that's currently not officially supported by this component. At the moment, new instances will end up closing the last instance due to the way the `Modal` is implemented. +Multiple `ConfirmDialog`s is an edge case that's currently not officially supported by this component. At the moment, new instances will end up closing the last instance due to the way the `Modal` is implemented. ## Custom Types From ba9f76cf9b7958cb7d9647577309829cdec188ab Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Mon, 21 Nov 2022 12:58:47 +0000 Subject: [PATCH 45/76] Site Logo: Apply width to logo container in editor (#45821) * Apply width to logo container * Move width to placeholder --- packages/block-library/src/site-logo/edit.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js index 89b9e3f6eba01e..8b85798baee712 100644 --- a/packages/block-library/src/site-logo/edit.js +++ b/packages/block-library/src/site-logo/edit.js @@ -512,6 +512,9 @@ export default function LogoEdit( { className={ placeholderClassName } preview={ logoImage } withIllustration={ true } + style={ { + width, + } } > { content } From 5122e650ab09604645011dd11cd2cfc83c00f70f Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:11:33 +0200 Subject: [PATCH 46/76] Components: Fix `no-node-access` in `Grid` tests (#45900) * Components: Fix no-node-access in Grid tests * role -> data-testid --- packages/components/src/grid/test/grid.tsx | 62 +++++++++++----------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/components/src/grid/test/grid.tsx b/packages/components/src/grid/test/grid.tsx index 791dfa4f52216c..150dac578c80ab 100644 --- a/packages/components/src/grid/test/grid.tsx +++ b/packages/components/src/grid/test/grid.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; /** * Internal dependencies @@ -12,14 +12,14 @@ import CONFIG from '../../utils/config-values'; describe( 'props', () => { test( 'should render correctly', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateColumns: 'repeat( 2, 1fr )', gap: `calc( ${ CONFIG.gridBase } * 3 )`, @@ -27,15 +27,15 @@ describe( 'props', () => { } ); test( 'should render gap', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateColumns: 'repeat( 3, 1fr )', gap: `calc( ${ CONFIG.gridBase } * 4 )`, @@ -43,60 +43,60 @@ describe( 'props', () => { } ); test( 'should render custom columns', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateColumns: 'repeat( 7, 1fr )', } ); } ); test( 'should render custom rows', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateRows: 'repeat( 7, 1fr )', } ); } ); test( 'should render align', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { alignItems: 'flex-start', display: 'grid', } ); } ); test( 'should render alignment spaced', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', alignItems: 'center', justifyContent: 'space-between', @@ -104,60 +104,60 @@ describe( 'props', () => { } ); test( 'should render justify', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', justifyContent: 'flex-start', } ); } ); test( 'should render isInline', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'inline-grid', gridTemplateColumns: 'repeat( 3, 1fr )', } ); } ); test( 'should render custom templateColumns', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateColumns: '1fr auto 1fr', } ); } ); test( 'should render custom templateRows', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateRows: '1fr auto 1fr', } ); From 37fc2a1dbc1671dabe3a4771b5b030814b452aae Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Mon, 21 Nov 2022 16:21:13 +0200 Subject: [PATCH 47/76] Enable easier drag and drop for navigation building (#45906) * tweaks the way navigation link and navigation submenu work to enable drag and drop in list view * Adds deps list to effects for submenu and navigation link blocks. Co-authored-by: Daniel Richards <677833+talldan@users.noreply.github.com> Co-authored-by: Maggie <3593343+MaggieCabrera@users.noreply.github.com> Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> Co-authored-by: Daniel Richards <677833+talldan@users.noreply.github.com> Co-authored-by: Maggie <3593343+MaggieCabrera@users.noreply.github.com> Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> --- .../block-library/src/navigation-link/edit.js | 25 +++++++++++++++++-- .../src/navigation-submenu/edit.js | 7 ++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index ed0acced4efe88..30a2f9ebb4b520 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -32,6 +32,7 @@ import { useBlockProps, store as blockEditorStore, getColorClassName, + useInnerBlocksProps, } from '@wordpress/block-editor'; import { isURL, prependHTTP, safeDecodeURI } from '@wordpress/url'; import { @@ -527,7 +528,9 @@ export default function NavigationLinkEdit( { const newSubmenu = createBlock( 'core/navigation-submenu', attributes, - innerBlocks + innerBlocks.length > 0 + ? innerBlocks + : [ createBlock( 'core/navigation-link' ) ] ); replaceBlock( clientId, newSubmenu ); } @@ -540,11 +543,14 @@ export default function NavigationLinkEdit( { if ( ! url ) { setIsLinkOpen( true ); } + }, [ url ] ); + + useEffect( () => { // If block has inner blocks, transform to Submenu. if ( hasChildren ) { transformToSubmenu(); } - }, [] ); + }, [ hasChildren ] ); /** * The hook shouldn't be necessary but due to a focus loss happening @@ -675,6 +681,20 @@ export default function NavigationLinkEdit( { onKeyDown, } ); + const ALLOWED_BLOCKS = [ + 'core/navigation-link', + 'core/navigation-submenu', + ]; + const DEFAULT_BLOCK = { + name: 'core/navigation-link', + }; + const innerBlocksProps = useInnerBlocksProps( blockProps, { + allowedBlocks: ALLOWED_BLOCKS, + __experimentalDefaultBlock: DEFAULT_BLOCK, + __experimentalDirectInsert: true, + renderAppender: false, + } ); + if ( ! url || isInvalid || isDraft ) { blockProps.onClick = () => setIsLinkOpen( true ); } @@ -915,6 +935,7 @@ export default function NavigationLinkEdit( { ) } +
); diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index c1dd94a0c10343..9810c7ebab05c6 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -541,6 +541,13 @@ export default function NavigationSubmenuEdit( { replaceBlock( clientId, newLinkBlock ); } + useEffect( () => { + // If block is empty, transform to Navigation Link. + if ( ! hasChildren ) { + transformToLink(); + } + }, [ hasChildren ] ); + const canConvertToLink = ! selectedBlockHasChildren || onlyDescendantIsEmptyLink; From f02087f7f8878a566b63cc02493ee3b24c855e34 Mon Sep 17 00:00:00 2001 From: chad1008 <13856531+chad1008@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:03:14 -0500 Subject: [PATCH 48/76] Components: Remove CircleIndicatorWrapper `focus-visible` outline (#45758) --- packages/components/CHANGELOG.md | 3 +++ .../styles/angle-picker-control-styles.js | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 2e47db6c778689..c57f3b01d58256 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -13,6 +13,9 @@ - `ColorPalette`, `BorderBox`, `BorderBoxControl`: polish and DRY prop types, add default values ([#45463](https://github.com/WordPress/gutenberg/pull/45463)). - `TabPanel`: Add ability to set icon only tab buttons ([#45005](https://github.com/WordPress/gutenberg/pull/45005)). +### Internal +- `AnglePickerControl`: remove `:focus-visible' outline on `CircleOutlineWrapper` ([#45758](https://github.com/WordPress/gutenberg/pull/45758)) + ### Bug Fix - `FormTokenField`: Fix duplicate input in IME composition ([#45607](https://github.com/WordPress/gutenberg/pull/45607)). diff --git a/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js b/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js index 1a7ed713d72f18..29b38850c055f4 100644 --- a/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js +++ b/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js @@ -42,6 +42,10 @@ export const CircleIndicatorWrapper = styled.div` position: relative; width: 100%; height: 100%; + + :focus-visible { + outline: none; + } `; export const CircleIndicator = styled.div` From 6fa02c7e364424a907838dde14815d771e6e483a Mon Sep 17 00:00:00 2001 From: hiyascout <113260898+hiyascout@users.noreply.github.com> Date: Mon, 21 Nov 2022 16:58:43 -0800 Subject: [PATCH 49/76] Update applying-styles-with-stylesheets.md (#45925) Fixed two typos in opening paragraph. ('someway' and 'this guides walks') --- .../block-tutorial/applying-styles-with-stylesheets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md b/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md index 815adfdf80c26d..e668a6ac762388 100644 --- a/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md @@ -2,7 +2,7 @@ ## Overview -A block typically inserts markup (HTML) into post content that you want to style in someway. This guides walks through a few different ways you can use CSS with the block editor and how to work with styles and stylesheets. +A block typically inserts markup (HTML) into post content that you want to style in some way. This guide walks through a few different ways you can use CSS with the block editor and how to work with styles and stylesheets. ## Before you start From 453866d96b2b1857fd8a2bc9340f3856336c07c8 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Tue, 22 Nov 2022 10:39:14 +0200 Subject: [PATCH 50/76] Add a starting page for page list block's hierarchy (#45861) * adds a rootPageID attribute and handling * adds UI to control rootPageID * Rename root to parent, generate fixtures Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> Co-authored-by: Joen A. <1204802+jasmussen@users.noreply.github.com> * PHP lint * PHP Lint Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> Co-authored-by: Joen A. <1204802+jasmussen@users.noreply.github.com> --- docs/reference-guides/core-blocks.md | 2 +- .../block-library/src/page-list/block.json | 7 +- packages/block-library/src/page-list/edit.js | 65 +++++++++++++++++-- .../block-library/src/page-list/index.php | 9 +++ .../fixtures/blocks/core__page-list.json | 4 +- 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 65e3cb1f65c8fc..8650c9d7d32749 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -429,7 +429,7 @@ Display a list of all pages. ([Source](https://github.com/WordPress/gutenberg/tr - **Name:** core/page-list - **Category:** widgets - **Supports:** ~~html~~, ~~reusable~~ -- **Attributes:** +- **Attributes:** rootPageID ## Paragraph diff --git a/packages/block-library/src/page-list/block.json b/packages/block-library/src/page-list/block.json index 3068a1fb8bc008..144cecb428396c 100644 --- a/packages/block-library/src/page-list/block.json +++ b/packages/block-library/src/page-list/block.json @@ -7,7 +7,12 @@ "description": "Display a list of all pages.", "keywords": [ "menu", "navigation" ], "textdomain": "default", - "attributes": {}, + "attributes": { + "rootPageID": { + "type": "integer", + "default": 0 + } + }, "usesContext": [ "textColor", "customTextColor", diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js index 71cbfa437a1f9a..ac7ed6162cf975 100644 --- a/packages/block-library/src/page-list/edit.js +++ b/packages/block-library/src/page-list/edit.js @@ -7,11 +7,18 @@ import classnames from 'classnames'; * WordPress dependencies */ import { + InspectorControls, BlockControls, useBlockProps, getColorClassName, } from '@wordpress/block-editor'; -import { ToolbarButton, Spinner, Notice } from '@wordpress/components'; +import { + PanelBody, + ToolbarButton, + Spinner, + Notice, + ComboboxControl, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useMemo, useState, memo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; @@ -27,7 +34,13 @@ import { ItemSubmenuIcon } from '../navigation-link/icons'; // Performance of Navigation Links is not good past this value. const MAX_PAGE_COUNT = 100; -export default function PageListEdit( { context, clientId } ) { +export default function PageListEdit( { + context, + clientId, + attributes, + setAttributes, +} ) { + const { parentPageID } = attributes; const { pagesByParentId, totalPages, hasResolvedPages } = usePageData(); const isNavigationChild = 'showSubmenuIcon' in context; @@ -86,6 +99,7 @@ export default function PageListEdit( { context, clientId } ) {
@@ -93,8 +107,35 @@ export default function PageListEdit( { context, clientId } ) { } }; + const useParentOptions = () => { + const [ pages ] = useGetPages(); + return pages?.reduce( ( accumulator, page ) => { + accumulator.push( { + value: page.id, + label: page.title.rendered, + } ); + return accumulator; + }, [] ); + }; + return ( <> + + + + setAttributes( { parentPageID: value ?? 0 } ) + } + help={ __( + 'Choose a page to show only its subpages.' + ) } + /> + + { allowConvertToLinks && ( @@ -129,7 +170,7 @@ function useFrontPageId() { }, [] ); } -function usePageData() { +function useGetPages() { const { records: pages, hasResolved: hasResolvedPages } = useEntityRecords( 'postType', 'page', @@ -142,10 +183,21 @@ function usePageData() { } ); + return [ pages, hasResolvedPages ]; +} + +function usePageData( pageId = 0 ) { + const [ pages, hasResolvedPages ] = useGetPages(); + return useMemo( () => { // TODO: Once the REST API supports passing multiple values to // 'orderby', this can be removed. // https://core.trac.wordpress.org/ticket/39037 + + if ( pageId !== 0 ) { + return pages.find( ( page ) => page.id === pageId ); + } + const sortedPages = [ ...( pages ?? [] ) ].sort( ( a, b ) => { if ( a.menu_order === b.menu_order ) { return a.title.rendered.localeCompare( b.title.rendered ); @@ -167,7 +219,7 @@ function usePageData() { hasResolvedPages, totalPages: pages?.length ?? null, }; - }, [ pages, hasResolvedPages ] ); + }, [ pageId, pages, hasResolvedPages ] ); } const PageItems = memo( function PageItems( { @@ -176,7 +228,10 @@ const PageItems = memo( function PageItems( { parentId = 0, depth = 0, } ) { - const pages = pagesByParentId.get( parentId ); + const parentPage = usePageData( parentId ); + const pages = pagesByParentId.get( parentId ) + ? pagesByParentId.get( parentId ) + : [ parentPage ]; const frontPageId = useFrontPageId(); if ( ! pages?.length ) { diff --git a/packages/block-library/src/page-list/index.php b/packages/block-library/src/page-list/index.php index 32acdad45ed635..7944f6f3a84ad6 100644 --- a/packages/block-library/src/page-list/index.php +++ b/packages/block-library/src/page-list/index.php @@ -252,6 +252,8 @@ function render_block_core_page_list( $attributes, $content, $block ) { static $block_id = 0; ++$block_id; + $parent_page_id = $attributes['parentPageID']; + $all_pages = get_pages( array( 'sort_column' => 'menu_order,post_title', @@ -306,6 +308,13 @@ function render_block_core_page_list( $attributes, $content, $block ) { $nested_pages = block_core_page_list_nest_pages( $top_level_pages, $pages_with_children ); + if ( 0 !== $parent_page_id ) { + $nested_pages = block_core_page_list_nest_pages( + $pages_with_children[ $parent_page_id ], + $pages_with_children + ); + } + $is_navigation_child = array_key_exists( 'showSubmenuIcon', $block->context ); $open_submenus_on_click = array_key_exists( 'openSubmenusOnClick', $block->context ) ? $block->context['openSubmenusOnClick'] : false; diff --git a/test/integration/fixtures/blocks/core__page-list.json b/test/integration/fixtures/blocks/core__page-list.json index 0bb67d8e44efd7..52e22aa9059abd 100644 --- a/test/integration/fixtures/blocks/core__page-list.json +++ b/test/integration/fixtures/blocks/core__page-list.json @@ -2,7 +2,9 @@ { "name": "core/page-list", "isValid": true, - "attributes": {}, + "attributes": { + "rootPageID": 0 + }, "innerBlocks": [] } ] From 20cc4aa86e3120449bad0bbf39e5bfb792376da5 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:27:39 +0200 Subject: [PATCH 51/76] Lodash: Simplify a few isEmpty calls (#45525) --- packages/block-library/src/audio/edit.native.js | 3 +-- packages/block-library/src/embed/embed-preview.native.js | 3 +-- packages/block-library/src/gallery/gallery.native.js | 3 +-- packages/block-library/src/gallery/v1/gallery.native.js | 3 +-- packages/block-library/src/video/edit.native.js | 3 +-- .../components/src/mobile/bottom-sheet/switch-cell.native.js | 4 ++-- packages/editor/src/components/post-title/index.native.js | 5 ++--- 7 files changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/block-library/src/audio/edit.native.js b/packages/block-library/src/audio/edit.native.js index 30ee44ff3f9487..fbc39ba190d3e2 100644 --- a/packages/block-library/src/audio/edit.native.js +++ b/packages/block-library/src/audio/edit.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { TouchableWithoutFeedback } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -228,7 +227,7 @@ function AudioEdit( { - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty Audio caption. */ __( 'Audio caption. Empty' ) : sprintf( diff --git a/packages/block-library/src/embed/embed-preview.native.js b/packages/block-library/src/embed/embed-preview.native.js index bb8df663baf281..87abc38348d56a 100644 --- a/packages/block-library/src/embed/embed-preview.native.js +++ b/packages/block-library/src/embed/embed-preview.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { TouchableWithoutFeedback } from 'react-native'; -import { isEmpty } from 'lodash'; import classnames from 'classnames/dedupe'; /** @@ -52,7 +51,7 @@ const EmbedPreview = ( { styles[ `embed-preview__sandbox--align-${ align }` ]; function accessibilityLabelCreator( caption ) { - return isEmpty( caption ) + return ! caption ? /* translators: accessibility text. Empty Embed caption. */ __( 'Embed caption. Empty' ) : sprintf( diff --git a/packages/block-library/src/gallery/gallery.native.js b/packages/block-library/src/gallery/gallery.native.js index 16c504f6ccc19a..a5174d0215c424 100644 --- a/packages/block-library/src/gallery/gallery.native.js +++ b/packages/block-library/src/gallery/gallery.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View } from 'react-native'; -import { isEmpty } from 'lodash'; /** * Internal dependencies @@ -100,7 +99,7 @@ export const Gallery = ( props ) => { isSelected={ isCaptionSelected } accessible={ true } accessibilityLabelCreator={ ( caption ) => - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty gallery caption. */ 'Gallery caption. Empty' diff --git a/packages/block-library/src/gallery/v1/gallery.native.js b/packages/block-library/src/gallery/v1/gallery.native.js index 7908d17988a1a4..c1d13cb6313e06 100644 --- a/packages/block-library/src/gallery/v1/gallery.native.js +++ b/packages/block-library/src/gallery/v1/gallery.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View } from 'react-native'; -import { isEmpty } from 'lodash'; /** * Internal dependencies @@ -142,7 +141,7 @@ export const Gallery = ( props ) => { isSelected={ isCaptionSelected } accessible={ true } accessibilityLabelCreator={ ( caption ) => - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty gallery caption. */ 'Gallery caption. Empty' : sprintf( diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index fb4937ce5da733..99225ba5d5c569 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View, TouchableWithoutFeedback, Text } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -367,7 +366,7 @@ class VideoEdit extends Component { - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty video caption. */ __( 'Video caption. Empty' ) : sprintf( diff --git a/packages/components/src/mobile/bottom-sheet/switch-cell.native.js b/packages/components/src/mobile/bottom-sheet/switch-cell.native.js index b49f03902ddb85..9dcaf794df750b 100644 --- a/packages/components/src/mobile/bottom-sheet/switch-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/switch-cell.native.js @@ -2,7 +2,7 @@ * External dependencies */ import { Switch } from 'react-native'; -import { isEmpty } from 'lodash'; + /** * WordPress dependencies */ @@ -20,7 +20,7 @@ export default function BottomSheetSwitchCell( props ) { }; const getAccessibilityLabel = () => { - if ( isEmpty( cellProps.help ) ) { + if ( ! cellProps.help ) { return value ? sprintf( /* translators: accessibility text. Switch setting ON state. %s: Switch title. */ diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 0d1d87c81e417e..4da9d8c308110b 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -80,7 +79,7 @@ class PostTitle extends Component { getTitle( title, postType ) { if ( 'page' === postType ) { - return isEmpty( title ) + return ! title ? /* translators: accessibility text. empty page title. */ __( 'Page title. Empty' ) : sprintf( @@ -90,7 +89,7 @@ class PostTitle extends Component { ); } - return isEmpty( title ) + return ! title ? /* translators: accessibility text. empty post title. */ __( 'Post title. Empty' ) : sprintf( From a7be92761e4707fdf9de6fd5337862d82af02133 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:30:08 +0200 Subject: [PATCH 52/76] Lodash: Refactor away from `_.pick()` in block library (#45940) * Lodash: Refactor away from _.pick() in block library * Add missing empty section check to updateSelectedCell() * Revert "Add missing empty section check to updateSelectedCell()" This reverts commit 9e9bea5766d801d82a8b48cd5c57a45630433721. * Improve precision of approach * Null-proofing --- packages/block-library/src/gallery/shared.js | 9 ++++-- .../block-library/src/gallery/v1/shared.js | 9 ++++-- packages/block-library/src/image/edit.js | 9 ++++-- packages/block-library/src/image/image.js | 18 +++++++----- packages/block-library/src/site-logo/edit.js | 13 +++++++-- packages/block-library/src/table/state.js | 29 ++++++++++++++----- 6 files changed, 64 insertions(+), 23 deletions(-) diff --git a/packages/block-library/src/gallery/shared.js b/packages/block-library/src/gallery/shared.js index 4765b4395994f7..f3142bbbaf9da6 100644 --- a/packages/block-library/src/gallery/shared.js +++ b/packages/block-library/src/gallery/shared.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, pick } from 'lodash'; +import { get } from 'lodash'; /** * WordPress dependencies @@ -13,7 +13,12 @@ export function defaultColumnsNumber( imageCount ) { } export const pickRelevantMediaFiles = ( image, sizeSlug = 'large' ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link' ] ); + const imageProps = Object.fromEntries( + Object.entries( image ?? {} ).filter( ( [ key ] ) => + [ 'alt', 'id', 'link' ].includes( key ) + ) + ); + imageProps.url = get( image, [ 'sizes', sizeSlug, 'url' ] ) || get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ) || diff --git a/packages/block-library/src/gallery/v1/shared.js b/packages/block-library/src/gallery/v1/shared.js index 484020cb9d58cf..9a0957cc7a120d 100644 --- a/packages/block-library/src/gallery/v1/shared.js +++ b/packages/block-library/src/gallery/v1/shared.js @@ -1,10 +1,15 @@ /** * External dependencies */ -import { get, pick } from 'lodash'; +import { get } from 'lodash'; export const pickRelevantMediaFiles = ( image, sizeSlug = 'large' ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); + const imageProps = Object.fromEntries( + Object.entries( image ?? {} ).filter( ( [ key ] ) => + [ 'alt', 'id', 'link', 'caption' ].includes( key ) + ) + ); + imageProps.url = get( image, [ 'sizes', sizeSlug, 'url' ] ) || get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ) || diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index baba2cee24ec33..4e504896b47094 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { get, isEmpty, pick } from 'lodash'; +import { get, isEmpty } from 'lodash'; /** * WordPress dependencies @@ -58,7 +58,12 @@ import { } from './constants'; export const pickRelevantMediaFiles = ( image, size ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); + const imageProps = Object.fromEntries( + Object.entries( image ?? {} ).filter( ( [ key ] ) => + [ 'alt', 'id', 'link', 'caption' ].includes( key ) + ) + ); + imageProps.url = get( image, [ 'sizes', size, 'url' ] ) || get( image, [ 'media_details', 'sizes', size, 'source_url' ] ) || diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 7245ce232c64b7..1e4881181701b4 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, filter, isEmpty, map, pick } from 'lodash'; +import { get, filter, isEmpty, map } from 'lodash'; /** * WordPress dependencies @@ -135,12 +135,16 @@ export default function Image( { } = select( blockEditorStore ); const rootClientId = getBlockRootClientId( clientId ); - const settings = pick( getSettings(), [ - 'imageEditing', - 'imageSizes', - 'maxWidth', - 'mediaUpload', - ] ); + const settings = Object.fromEntries( + Object.entries( getSettings() ).filter( ( [ key ] ) => + [ + 'imageEditing', + 'imageSizes', + 'maxWidth', + 'mediaUpload', + ].includes( key ) + ) + ); return { ...settings, diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js index 8b85798baee712..0d752a5dbae31f 100644 --- a/packages/block-library/src/site-logo/edit.js +++ b/packages/block-library/src/site-logo/edit.js @@ -2,7 +2,6 @@ * External dependencies */ import classnames from 'classnames'; -import { pick } from 'lodash'; /** * WordPress dependencies @@ -87,7 +86,11 @@ const SiteLogo = ( { ); return { title: siteEntities?.name, - ...pick( getSettings(), [ 'imageEditing', 'maxWidth' ] ), + ...Object.fromEntries( + Object.entries( getSettings() ).filter( ( [ key ] ) => + [ 'imageEditing', 'maxWidth' ].includes( key ) + ) + ), }; }, [] ); @@ -121,7 +124,11 @@ const SiteLogo = ( { alt={ alt } onLoad={ ( event ) => { setNaturalSize( - pick( event.target, [ 'naturalWidth', 'naturalHeight' ] ) + Object.fromEntries( + Object.entries( event.target ).filter( ( [ key ] ) => + [ 'naturalWidth', 'naturalHeight' ].includes( key ) + ) + ) ); } } /> diff --git a/packages/block-library/src/table/state.js b/packages/block-library/src/table/state.js index 68fee2d0bda9b5..a1aafc31459365 100644 --- a/packages/block-library/src/table/state.js +++ b/packages/block-library/src/table/state.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, mapValues, pick } from 'lodash'; +import { get, mapValues } from 'lodash'; const INHERITED_COLUMN_ATTRIBUTES = [ 'align' ]; @@ -78,7 +78,11 @@ export function updateSelectedCell( state, selection, updateCell ) { return state; } - const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const tableSections = Object.fromEntries( + Object.entries( state ).filter( ( [ key ] ) => + [ 'head', 'body', 'foot' ].includes( key ) + ) + ); const { sectionName: selectionSectionName, rowIndex: selectionRowIndex } = selection; @@ -174,9 +178,12 @@ export function insertRow( state, { sectionName, rowIndex, columnCount } ) { [ 'cells', index ], {} ); - const inheritedAttributes = pick( - firstCellInColumn, - INHERITED_COLUMN_ATTRIBUTES + + const inheritedAttributes = Object.fromEntries( + Object.entries( firstCellInColumn ).filter( + ( [ key ] ) => + INHERITED_COLUMN_ATTRIBUTES.includes( key ) + ) ); return { @@ -220,7 +227,11 @@ export function deleteRow( state, { sectionName, rowIndex } ) { * @return {Object} New table state. */ export function insertColumn( state, { columnIndex } ) { - const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const tableSections = Object.fromEntries( + Object.entries( state ).filter( ( [ key ] ) => + [ 'head', 'body', 'foot' ].includes( key ) + ) + ); return mapValues( tableSections, ( section, sectionName ) => { // Bail early if the table section is empty. @@ -259,7 +270,11 @@ export function insertColumn( state, { columnIndex } ) { * @return {Object} New table state. */ export function deleteColumn( state, { columnIndex } ) { - const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const tableSections = Object.fromEntries( + Object.entries( state ).filter( ( [ key ] ) => + [ 'head', 'body', 'foot' ].includes( key ) + ) + ); return mapValues( tableSections, ( section ) => { // Bail early if the table section is empty. From 4c21122dab619ece1678bdfb41b1fc08304028df Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 22 Nov 2022 20:53:24 +1100 Subject: [PATCH 53/76] Style engine: trim multiple selector strings (#45873) * Trim multiple selector string, that is a string of selectors separated by a comma, passed to the style engine. This is so we can correctly align prettified content, and remove spaces between multiple selectors with the same styles. * Only remove selector white spaces when $should_prettify is true * Updated test --- .../class-wp-style-engine-css-rule.php | 6 +++-- .../class-wp-style-engine-css-rule-test.php | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rule.php b/packages/style-engine/class-wp-style-engine-css-rule.php index e1605517573313..76ba12673aeda5 100644 --- a/packages/style-engine/class-wp-style-engine-css-rule.php +++ b/packages/style-engine/class-wp-style-engine-css-rule.php @@ -113,8 +113,10 @@ public function get_css( $should_prettify = false, $indent_count = 0 ) { $declarations_indent = $should_prettify ? $indent_count + 1 : 0; $suffix = $should_prettify ? "\n" : ''; $spacer = $should_prettify ? ' ' : ''; - $selector = $should_prettify ? str_replace( ',', ",\n", $this->get_selector() ) : $this->get_selector(); - $css_declarations = $this->declarations->get_declarations_string( $should_prettify, $declarations_indent ); + // Trims any multiple selectors strings. + $selector = $should_prettify ? implode( ',', array_map( 'trim', explode( ',', $this->get_selector() ) ) ) : $this->get_selector(); + $selector = $should_prettify ? str_replace( array( ',' ), ",\n", $selector ) : $selector; + $css_declarations = $this->declarations->get_declarations_string( $should_prettify, $declarations_indent ); if ( empty( $css_declarations ) ) { return ''; diff --git a/phpunit/style-engine/class-wp-style-engine-css-rule-test.php b/phpunit/style-engine/class-wp-style-engine-css-rule-test.php index 02482c70e4d84a..444c83f150f911 100644 --- a/phpunit/style-engine/class-wp-style-engine-css-rule-test.php +++ b/phpunit/style-engine/class-wp-style-engine-css-rule-test.php @@ -142,4 +142,31 @@ public function test_should_prettify_css_rule_output() { $this->assertSame( $expected, $css_rule->get_css( true ) ); } + + /** + * Tests that a string of multiple selectors is trimmed. + * + * @covers ::get_css + */ + public function test_should_trim_multiple_selectors() { + $selector = '.poirot, .poirot:active, #miss-marple > .st-mary-mead '; + $input_declarations = array( + 'margin-left' => '0', + 'font-family' => 'Detective Sans', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations_Gutenberg( $input_declarations ); + $css_rule = new WP_Style_Engine_CSS_Rule_Gutenberg( $selector, $css_declarations ); + $expected = '.poirot, .poirot:active, #miss-marple > .st-mary-mead {margin-left:0;font-family:Detective Sans;}'; + + $this->assertSame( $expected, $css_rule->get_css(), 'Return value should be not prettified.' ); + + $expected_prettified = '.poirot, +.poirot:active, +#miss-marple > .st-mary-mead { + margin-left: 0; + font-family: Detective Sans; +}'; + + $this->assertSame( $expected_prettified, $css_rule->get_css( true ), 'Return value should be prettified with new lines and indents.' ); + } } From 516d05c1b370665e44b153be32a8f96d6ba902df Mon Sep 17 00:00:00 2001 From: Artemio Morales Date: Tue, 22 Nov 2022 05:13:29 -0500 Subject: [PATCH 54/76] Clarify explanation of how 'Convert to Links' works in Page List block (#45394) * Clarify explanation of how 'Convert to Links' works * Remove splitting of translated string * Update string to use createInterpolateElement to ensure proper translation * Simplify and clarify language in Page List edit modal * Remove tip from modal copy --- .../src/page-list/convert-to-links-modal.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/block-library/src/page-list/convert-to-links-modal.js b/packages/block-library/src/page-list/convert-to-links-modal.js index bbb3e1e44e98b1..826539ec378348 100644 --- a/packages/block-library/src/page-list/convert-to-links-modal.js +++ b/packages/block-library/src/page-list/convert-to-links-modal.js @@ -91,18 +91,13 @@ export default function ConvertToLinksModal( { onClose, clientId } ) {

{ __( - 'To edit this navigation menu, convert it to single page links. This allows you to add, re-order, remove items, or edit their labels.' - ) } -

-

- { __( - "Note: if you add new pages to your site, you'll need to add them to your navigation menu." + 'This menu is automatically kept in sync with pages on your site. You can manage the menu yourself by clicking customize below.' ) }

@@ -119,7 +114,7 @@ export default function ConvertToLinksModal( { onClose, clientId } ) { createBlock: create, } ) } > - { __( 'Convert' ) } + { __( 'Customize' ) }
From 6cedad20b0d96ac650b17d37b6071a5d7dbad881 Mon Sep 17 00:00:00 2001 From: Maggie Date: Tue, 22 Nov 2022 11:55:52 +0100 Subject: [PATCH 55/76] Include offcanvas specific styles (#45963) Co-authored-by: Dave Smith --- .../components/off-canvas-editor/style.scss | 398 +----------------- packages/block-editor/src/style.scss | 3 + 2 files changed, 4 insertions(+), 397 deletions(-) diff --git a/packages/block-editor/src/components/off-canvas-editor/style.scss b/packages/block-editor/src/components/off-canvas-editor/style.scss index 9782dc7027bc7c..7f22f6f5340cfd 100644 --- a/packages/block-editor/src/components/off-canvas-editor/style.scss +++ b/packages/block-editor/src/components/off-canvas-editor/style.scss @@ -1,397 +1 @@ -.block-editor-list-view-tree { - width: 100%; - border-collapse: collapse; - padding: 0; - margin: 0; - - // Move upwards when in modal. - .components-modal__content & { - margin: (-$grid-unit-15) (-$grid-unit-15 * 0.5) 0; - width: calc(100% + #{ $grid-unit-15 }); - } -} - -.block-editor-list-view-leaf { - // Use position relative for row animation. - position: relative; - - // The background has to be applied to the td, not tr, or border-radius won't work. - &.is-selected td { - background: var(--wp-admin-theme-color); - } - &.is-selected .block-editor-list-view-block-contents, - &.is-selected .components-button.has-icon { - color: $white; - } - &.is-selected .block-editor-list-view-block-contents { - // Hide selection styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - background: none; - color: $gray-900; - } - } - &.is-selected .block-editor-list-view-block-contents:focus { - &::after { - box-shadow: - inset 0 0 0 1px $white, - 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - } - } - &.is-selected .block-editor-list-view-block__menu:focus { - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) $white; - } - - &.is-dragging { - display: none; - } - - // Border radius for corners of the selected item. - &.is-first-selected td:first-child { - border-top-left-radius: $radius-block-ui; - } - &.is-first-selected td:last-child { - border-top-right-radius: $radius-block-ui; - } - &.is-last-selected td:first-child { - border-bottom-left-radius: $radius-block-ui; - } - &.is-last-selected td:last-child { - border-bottom-right-radius: $radius-block-ui; - } - &.is-branch-selected:not(.is-selected) { - // Lighten a CSS variable without introducing a new SASS variable - background: - linear-gradient(transparentize($white, 0.1), transparentize($white, 0.1)), - linear-gradient(var(--wp-admin-theme-color), var(--wp-admin-theme-color)); - } - &.is-branch-selected.is-first-selected td:first-child { - border-top-left-radius: $radius-block-ui; - } - &.is-branch-selected.is-first-selected td:last-child { - border-top-right-radius: $radius-block-ui; - } - &[aria-expanded="false"] { - &.is-branch-selected.is-first-selected td:first-child { - border-top-left-radius: $radius-block-ui; - } - &.is-branch-selected.is-first-selected td:last-child { - border-top-right-radius: $radius-block-ui; - } - &.is-branch-selected.is-last-selected td:first-child { - border-bottom-left-radius: $radius-block-ui; - } - &.is-branch-selected.is-last-selected td:last-child { - border-bottom-right-radius: $radius-block-ui; - } - } - &.is-branch-selected:not(.is-selected) td { - border-radius: 0; - } - - - // List View renders a fixed number of items and relies on each item having a fixed height of 36px. - // If this value changes, we should also change the itemHeight value set in useFixedWindowList. - // See: https://github.com/WordPress/gutenberg/pull/35230 for additional context. - .block-editor-list-view-block-contents { - display: flex; - align-items: center; - width: 100%; - height: auto; - padding: ($grid-unit-15 * 0.5) $grid-unit-05 ($grid-unit-15 * 0.5) 0; - text-align: left; - color: $gray-900; - border-radius: $radius-block-ui; - position: relative; - white-space: nowrap; - - &.is-dropping-before::before { - content: ""; - position: absolute; - pointer-events: none; - transition: border-color 0.1s linear, border-style 0.1s linear, box-shadow 0.1s linear; - top: -2px; - right: 0; - left: 0; - border-top: 4px solid var(--wp-admin-theme-color); - } - - .components-modal__content & { - padding-left: 0; - padding-right: 0; - } - } - - .block-editor-list-view-block-contents:focus { - box-shadow: none; - - &::after { - content: ""; - position: absolute; - top: 0; - right: -(24px + 5px); // Icon size + padding. - bottom: 0; - left: 0; - border-radius: inherit; - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - z-index: 2; - pointer-events: none; - - // Hide focus styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - box-shadow: none; - } - } - } - // Fix focus styling width when one row has fewer cells. - &.has-single-cell .block-editor-list-view-block-contents:focus::after { - right: 0; - } - - .block-editor-list-view-block__menu:focus { - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - z-index: 1; - - // Hide focus styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - box-shadow: none; - } - } - - &.is-visible .block-editor-list-view-block-contents { - opacity: 1; - @include edit-post__fade-in-animation; - } - - .block-editor-block-icon { - align-self: flex-start; - margin-right: $grid-unit-10; - width: $icon-size; - } - - .block-editor-list-view-block__menu-cell, - .block-editor-list-view-block__mover-cell, - .block-editor-list-view-block__contents-cell { - padding-top: 0; - padding-bottom: 0; - } - - .block-editor-list-view-block__menu-cell, - .block-editor-list-view-block__mover-cell { - line-height: 0; - width: $button-size; - vertical-align: middle; - @include reduce-motion("transition"); - - > * { - opacity: 0; - } - - // Show on hover, visible, and show above to keep the hit area size. - &:hover, - &.is-visible { - position: relative; - z-index: 1; - - > * { - opacity: 1; - @include edit-post__fade-in-animation; - } - } - - &, - .components-button.has-icon { - width: 24px; - min-width: 24px; - padding: 0; - } - } - - .block-editor-list-view-block__menu-cell { - padding-right: $grid-unit-05; - - .components-button.has-icon { - height: 24px; - } - } - - .block-editor-list-view-block__mover-cell-alignment-wrapper { - display: flex; - height: 100%; - flex-direction: column; - align-items: center; - } - - // Keep the tap target large but the focus target small. - .block-editor-block-mover-button { - position: relative; - width: $button-size; - height: $button-size-small; - - // Position the icon. - svg { - position: relative; - height: $button-size-small; - } - - &.is-up-button { - margin-top: -$grid-unit-15 * 0.5; - align-items: flex-end; - svg { - bottom: -$grid-unit-05; - } - } - - &.is-down-button { - margin-bottom: -$grid-unit-15 * 0.5; - align-items: flex-start; - svg { - top: -$grid-unit-05; - } - } - - // Tweak size and position of focus ring. - &::before { - height: 16px; - min-width: 100%; - left: 0; - right: 0; - } - } - - .block-editor-inserter__toggle { - background: $gray-900; - color: $white; - height: $grid-unit-30; - margin: 6px 6px 6px 1px; - min-width: $grid-unit-30; - - &:active { - color: $white; - } - } - - .block-editor-list-view-block-select-button__label-wrapper { - min-width: 120px; - } - - .block-editor-list-view-block-select-button__title { - flex: 1; - position: relative; - - .components-truncate { - position: absolute; - width: 100%; - transform: translateY(-50%); - } - } - - .block-editor-list-view-block-select-button__anchor-wrapper { - position: relative; - max-width: min(110px, 40%); - width: 100%; - } - - .block-editor-list-view-block-select-button__anchor { - position: absolute; - right: 0; - transform: translateY(-50%); - background: rgba($black, 0.1); - border-radius: $radius-block-ui; - padding: 2px 6px; - max-width: 100%; - box-sizing: border-box; - } - - &.is-selected .block-editor-list-view-block-select-button__anchor { - background: rgba($black, 0.3); - } - - .block-editor-list-view-block-select-button__lock { - line-height: 0; - width: 24px; - min-width: 24px; - margin-left: auto; - padding: 0; - vertical-align: middle; - } -} - -.block-editor-list-view-block-select-button__description, -.block-editor-list-view-appender__description { - display: none; -} - -.block-editor-list-view-block__contents-cell, -.block-editor-list-view-appender__cell { - .block-editor-list-view-block__contents-container, - .block-editor-list-view-appender__container { - display: flex; - } -} - -// Chevron container metrics. -.block-editor-list-view__expander { - height: $icon-size; - margin-left: $grid-unit-05; - width: $icon-size; -} - -// First level of indentation is aria-level 2, max indent is 8. -// Indent is a full icon size, plus 4px which optically aligns child icons to the text label above. -$block-navigation-max-indent: 8; -.block-editor-list-view-leaf[aria-level] .block-editor-list-view__expander { - margin-left: ( $icon-size ) * $block-navigation-max-indent + 4 * ( $block-navigation-max-indent - 1 ); -} - -.block-editor-list-view-leaf:not([aria-level="1"]) { - .block-editor-list-view__expander { - margin-right: 4px; - } -} - -@for $i from 0 to $block-navigation-max-indent { - .block-editor-list-view-leaf[aria-level="#{ $i + 1 }"] .block-editor-list-view__expander { - @if $i - 1 >= 0 { - margin-left: ( $icon-size * $i ) + 4 * ($i - 1); - } - @else { - margin-left: ( $icon-size * $i ); - } - } -} - -.block-editor-list-view-leaf .block-editor-list-view__expander { - visibility: hidden; -} - -// Point downwards when open. -.block-editor-list-view-leaf[aria-expanded="true"] .block-editor-list-view__expander svg { - visibility: visible; - transition: transform 0.2s ease; - transform: rotate(90deg); - @include reduce-motion("transition"); -} - -// Point rightwards when closed -.block-editor-list-view-leaf[aria-expanded="false"] .block-editor-list-view__expander svg { - visibility: visible; - transform: rotate(0deg); - transition: transform 0.2s ease; - @include reduce-motion("transition"); -} - -.block-editor-list-view-drop-indicator { - pointer-events: none; - - .block-editor-list-view-drop-indicator__line { - background: var(--wp-admin-theme-color); - height: $border-width; - } -} - -.block-editor-list-view-placeholder { - padding: 0; - margin: 0; - height: 36px; -} - +//Styles for off-canvas editor, remove this line when you add some css to this file! diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 853a030f045945..00737423611582 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -65,4 +65,7 @@ @import "./components/preview-options/style.scss"; @import "./components/spacing-sizes-control/style.scss"; +// Experiments. +@import "./components/off-canvas-editor/style.scss"; + @include wordpress-admin-schemes(); From 821db1c8fc598a8dd69b52a97fe843f691b5cfa4 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Tue, 22 Nov 2022 11:06:19 +0000 Subject: [PATCH 56/76] Navigation: Add label field to navigation link and navigation submenu (#45964) --- packages/block-library/src/navigation-link/edit.js | 8 ++++++++ packages/block-library/src/navigation-submenu/edit.js | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 30a2f9ebb4b520..0c08209130ba4c 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -737,6 +737,14 @@ export default function NavigationLinkEdit( { { /* Warning, this duplicated in packages/block-library/src/navigation-submenu/edit.js */ } + { + setAttributes( { label: labelValue } ); + } } + label={ __( 'Label' ) } + autoComplete="off" + /> { diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index 9810c7ebab05c6..aa508e63a62262 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -578,6 +578,14 @@ export default function NavigationSubmenuEdit( { { /* Warning, this duplicated in packages/block-library/src/navigation-link/edit.js */ } + { + setAttributes( { label: labelValue } ); + } } + label={ __( 'Label' ) } + autoComplete="off" + /> { From 367c180b384f6656aa6fa5f6926e0c39ab073905 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Tue, 22 Nov 2022 13:02:41 +0000 Subject: [PATCH 57/76] Page List: If no parent page is set, still render all children (#45967) * Page List: If no parent page is set, still render all children * Fixes the block not rendering with no parent by correctly renaming the new parentPageID attribute. Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> * regenerated fixtures for new attribute name Co-authored-by: Andrei Draganescu Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> --- docs/reference-guides/core-blocks.md | 2 +- packages/block-library/src/page-list/block.json | 2 +- test/integration/fixtures/blocks/core__page-list.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 8650c9d7d32749..09e48ebda0f65e 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -429,7 +429,7 @@ Display a list of all pages. ([Source](https://github.com/WordPress/gutenberg/tr - **Name:** core/page-list - **Category:** widgets - **Supports:** ~~html~~, ~~reusable~~ -- **Attributes:** rootPageID +- **Attributes:** parentPageID ## Paragraph diff --git a/packages/block-library/src/page-list/block.json b/packages/block-library/src/page-list/block.json index 144cecb428396c..2fc6993849d6f7 100644 --- a/packages/block-library/src/page-list/block.json +++ b/packages/block-library/src/page-list/block.json @@ -8,7 +8,7 @@ "keywords": [ "menu", "navigation" ], "textdomain": "default", "attributes": { - "rootPageID": { + "parentPageID": { "type": "integer", "default": 0 } diff --git a/test/integration/fixtures/blocks/core__page-list.json b/test/integration/fixtures/blocks/core__page-list.json index 52e22aa9059abd..b1992a437b8849 100644 --- a/test/integration/fixtures/blocks/core__page-list.json +++ b/test/integration/fixtures/blocks/core__page-list.json @@ -3,7 +3,7 @@ "name": "core/page-list", "isValid": true, "attributes": { - "rootPageID": 0 + "parentPageID": 0 }, "innerBlocks": [] } From d7e3cc12afac33be0b5b6eb9c85a27506cab4e5d Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 22 Nov 2022 15:36:31 +0200 Subject: [PATCH 58/76] Lodash: Refactor editor away from _.pick() (#45944) --- .../provider/use-block-editor-settings.js | 97 +++++++++---------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 51d16ef1b6a7d6..0b69dcc66eb81c 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { pick } from 'lodash'; - /** * WordPress dependencies */ @@ -133,50 +128,54 @@ function useBlockEditorSettings( settings, hasTemplate ) { return useMemo( () => ( { - ...pick( settings, [ - '__experimentalBlockDirectory', - '__experimentalDiscussionSettings', - '__experimentalFeatures', - '__experimentalPreferredStyleVariations', - '__experimentalSetIsInserterOpened', - '__unstableGalleryWithImageBlocks', - 'alignWide', - 'allowedBlockTypes', - 'bodyPlaceholder', - 'canLockBlocks', - 'codeEditingEnabled', - 'colors', - 'disableCustomColors', - 'disableCustomFontSizes', - 'disableCustomSpacingSizes', - 'disableCustomGradients', - 'disableLayoutStyles', - 'enableCustomLineHeight', - 'enableCustomSpacing', - 'enableCustomUnits', - 'focusMode', - 'fontSizes', - 'gradients', - 'generateAnchors', - 'hasFixedToolbar', - 'isDistractionFree', - 'hasInlineToolbar', - 'imageDefaultSize', - 'imageDimensions', - 'imageEditing', - 'imageSizes', - 'isRTL', - 'keepCaretInsideBlock', - 'maxWidth', - 'onUpdateDefaultBlockStyles', - 'styles', - 'template', - 'templateLock', - 'titlePlaceholder', - 'supportsLayout', - 'widgetTypesToHideFromLegacyWidgetBlock', - '__unstableResolvedAssets', - ] ), + ...Object.fromEntries( + Object.entries( settings ).filter( ( [ key ] ) => + [ + '__experimentalBlockDirectory', + '__experimentalDiscussionSettings', + '__experimentalFeatures', + '__experimentalPreferredStyleVariations', + '__experimentalSetIsInserterOpened', + '__unstableGalleryWithImageBlocks', + 'alignWide', + 'allowedBlockTypes', + 'bodyPlaceholder', + 'canLockBlocks', + 'codeEditingEnabled', + 'colors', + 'disableCustomColors', + 'disableCustomFontSizes', + 'disableCustomSpacingSizes', + 'disableCustomGradients', + 'disableLayoutStyles', + 'enableCustomLineHeight', + 'enableCustomSpacing', + 'enableCustomUnits', + 'focusMode', + 'fontSizes', + 'gradients', + 'generateAnchors', + 'hasFixedToolbar', + 'isDistractionFree', + 'hasInlineToolbar', + 'imageDefaultSize', + 'imageDimensions', + 'imageEditing', + 'imageSizes', + 'isRTL', + 'keepCaretInsideBlock', + 'maxWidth', + 'onUpdateDefaultBlockStyles', + 'styles', + 'template', + 'templateLock', + 'titlePlaceholder', + 'supportsLayout', + 'widgetTypesToHideFromLegacyWidgetBlock', + '__unstableResolvedAssets', + ].includes( key ) + ) + ), mediaUpload: hasUploadPermissions ? mediaUpload : undefined, __experimentalReusableBlocks: reusableBlocks, __experimentalBlockPatterns: blockPatterns, From c75fc6d4b8f09dfc4fb2286814cbccc737ccc40f Mon Sep 17 00:00:00 2001 From: Miguel Torres Date: Tue, 22 Nov 2022 17:19:07 +0100 Subject: [PATCH 59/76] Update `gutenberg_get_global_stylesheet` to use `WP_Object_Cache` (#45679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André <583546+oandregal@users.noreply.github.com> --- .../get-global-styles-and-settings.php | 85 -------------- .../class-wp-theme-json-resolver-6-2.php | 25 ++++ lib/compat/wordpress-6.2/default-filters.php | 10 ++ .../get-global-styles-and-settings.php | 110 ++++++++++++++++++ phpunit/wp-get-global-stylesheet-test.php | 98 ++++++++++++++++ 5 files changed, 243 insertions(+), 85 deletions(-) create mode 100644 phpunit/wp-get-global-stylesheet-test.php diff --git a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php index 35c540ce1c57a6..fd6113c7405c4a 100644 --- a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php @@ -54,88 +54,3 @@ function ( $item ) { } } } - -/** - * Returns the stylesheet resulting of merging core, theme, and user data. - * - * @param array $types Types of styles to load. Optional. - * It accepts 'variables', 'styles', 'presets' as values. - * If empty, it'll load all for themes with theme.json support - * and only [ 'variables', 'presets' ] for themes without theme.json support. - * - * @return string Stylesheet. - */ -function gutenberg_get_global_stylesheet( $types = array() ) { - // Return cached value if it can be used and exists. - // It's cached by theme to make sure that theme switching clears the cache. - $can_use_cached = ( - ( empty( $types ) ) && - ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && - ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && - ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && - ! is_admin() - ); - $transient_name = 'gutenberg_global_styles_' . get_stylesheet(); - if ( $can_use_cached ) { - $cached = get_transient( $transient_name ); - if ( $cached ) { - return $cached; - } - } - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); - $supports_theme_json = wp_theme_has_theme_json(); - if ( empty( $types ) && ! $supports_theme_json ) { - $types = array( 'variables', 'presets', 'base-layout-styles' ); - } elseif ( empty( $types ) ) { - $types = array( 'variables', 'styles', 'presets' ); - } - - /* - * If variables are part of the stylesheet, - * we add them. - * - * This is so themes without a theme.json still work as before 5.9: - * they can override the default presets. - * See https://core.trac.wordpress.org/ticket/54782 - */ - $styles_variables = ''; - if ( in_array( 'variables', $types, true ) ) { - /* - * We only use the default, theme, and custom origins. - * This is because styles for blocks origin are added - * at a later phase (render cycle) so we only render the ones in use. - * @see wp_add_global_styles_for_blocks - */ - $origins = array( 'default', 'theme', 'custom' ); - $styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins ); - $types = array_diff( $types, array( 'variables' ) ); - } - - /* - * For the remaining types (presets, styles), we do consider origins: - * - * - themes without theme.json: only the classes for the presets defined by core - * - themes with theme.json: the presets and styles classes, both from core and the theme - */ - $styles_rest = ''; - if ( ! empty( $types ) ) { - /* - * We only use the default, theme, and custom origins. - * This is because styles for blocks origin are added - * at a later phase (render cycle) so we only render the ones in use. - * @see wp_add_global_styles_for_blocks - */ - $origins = array( 'default', 'theme', 'custom' ); - if ( ! $supports_theme_json ) { - $origins = array( 'default' ); - } - $styles_rest = $tree->get_stylesheet( $types, $origins ); - } - $stylesheet = $styles_variables . $styles_rest; - if ( $can_use_cached ) { - // Cache for a minute. - // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. - set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS ); - } - return $stylesheet; -} diff --git a/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php b/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php index e10710e0f4709f..7adb37ffbd9e01 100644 --- a/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php +++ b/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php @@ -32,4 +32,29 @@ public static function theme_has_support() { return wp_theme_has_theme_json(); } + /** + * Private method to clean the cached data after an upgrade. + * + * It is hooked into the `upgrader_process_complete` action. + * + * @see default-filters.php + * + * @param WP_Upgrader $upgrader WP_Upgrader instance. + * @param array $options Array of bulk item update data. + */ + public static function _clean_cached_data_upon_upgrading( $upgrader, $options ) { + if ( 'update' !== $options['action'] ) { + return; + } + + if ( + 'core' === $options['type'] || + 'plugin' === $options['type'] || + // Clean cache only if the active theme was updated. + ( 'theme' === $options['type'] && ( isset( $options['themes'][ get_stylesheet() ] ) || isset( $options['themes'][ get_template() ] ) ) ) + ) { + static::clean_cached_data(); + } + } + } diff --git a/lib/compat/wordpress-6.2/default-filters.php b/lib/compat/wordpress-6.2/default-filters.php index 861b8a01421c62..927ff2bd12aa33 100644 --- a/lib/compat/wordpress-6.2/default-filters.php +++ b/lib/compat/wordpress-6.2/default-filters.php @@ -20,3 +20,13 @@ add_action( 'switch_theme', 'wp_theme_has_theme_json_clean_cache' ); add_action( 'start_previewing_theme', 'wp_theme_has_theme_json_clean_cache' ); add_action( 'upgrader_process_complete', '_wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme', 10, 2 ); +add_action( 'save_post_wp_global_styles', array( 'WP_Theme_JSON_Resolver_Gutenberg', 'clean_cached_data' ) ); +add_action( 'activated_plugin', array( 'WP_Theme_JSON_Resolver_Gutenberg', 'clean_cached_data' ) ); +add_action( 'deactivated_plugin', array( 'WP_Theme_JSON_Resolver_Gutenberg', 'clean_cached_data' ) ); +add_action( 'upgrader_process_complete', array( 'WP_Theme_JSON_Resolver_Gutenberg', '_clean_cached_data_upon_upgrading', 10, 2 ) ); +add_action( 'save_post_wp_global_styles', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'switch_theme', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'start_previewing_theme', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'activated_plugin', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'deactivated_plugin', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'upgrader_process_complete', '_gutenberg_get_global_stylesheet_clean_cache_upon_upgrading', 10, 2 ); diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index be9781556868c0..5d37d3182ccc85 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -79,3 +79,113 @@ function _wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme( $upgr } } } + +/** + * Returns the stylesheet resulting of merging core, theme, and user data. + * + * @param array $types Types of styles to load. Optional. + * It accepts 'variables', 'styles', 'presets' as values. + * If empty, it'll load all for themes with theme.json support + * and only [ 'variables', 'presets' ] for themes without theme.json support. + * + * @return string Stylesheet. + */ +function gutenberg_get_global_stylesheet( $types = array() ) { + // Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. + $can_use_cached = empty( $types ) && ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ); + $cache_key = 'gutenberg_get_global_stylesheet'; + $cache_group = 'theme_json'; + if ( $can_use_cached ) { + $cached = wp_cache_get( $cache_key, $cache_group ); + if ( $cached ) { + return $cached; + } + } + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); + $supports_theme_json = wp_theme_has_theme_json(); + if ( empty( $types ) && ! $supports_theme_json ) { + $types = array( 'variables', 'presets', 'base-layout-styles' ); + } elseif ( empty( $types ) ) { + $types = array( 'variables', 'styles', 'presets' ); + } + + /* + * If variables are part of the stylesheet, + * we add them. + * + * This is so themes without a theme.json still work as before 5.9: + * they can override the default presets. + * See https://core.trac.wordpress.org/ticket/54782 + */ + $styles_variables = ''; + if ( in_array( 'variables', $types, true ) ) { + /* + * We only use the default, theme, and custom origins. + * This is because styles for blocks origin are added + * at a later phase (render cycle) so we only render the ones in use. + * @see wp_add_global_styles_for_blocks + */ + $origins = array( 'default', 'theme', 'custom' ); + $styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins ); + $types = array_diff( $types, array( 'variables' ) ); + } + + /* + * For the remaining types (presets, styles), we do consider origins: + * + * - themes without theme.json: only the classes for the presets defined by core + * - themes with theme.json: the presets and styles classes, both from core and the theme + */ + $styles_rest = ''; + if ( ! empty( $types ) ) { + /* + * We only use the default, theme, and custom origins. + * This is because styles for blocks origin are added + * at a later phase (render cycle) so we only render the ones in use. + * @see wp_add_global_styles_for_blocks + */ + $origins = array( 'default', 'theme', 'custom' ); + if ( ! $supports_theme_json ) { + $origins = array( 'default' ); + } + $styles_rest = $tree->get_stylesheet( $types, $origins ); + } + $stylesheet = $styles_variables . $styles_rest; + if ( $can_use_cached ) { + wp_cache_set( $cache_key, $stylesheet, $cache_group ); + } + return $stylesheet; +} + +/** + * Clean the cache used by the `gutenberg_get_global_stylesheet` function. + */ +function gutenberg_get_global_stylesheet_clean_cache() { + wp_cache_delete( 'gutenberg_get_global_stylesheet', 'theme_json' ); +} + +/** + * Private function to clean the cache used by the `gutenberg_get_global_stylesheet` function after an upgrade. + * + * It is hooked into the `upgrader_process_complete` action. + * + * @see default-filters.php + * + * @param WP_Upgrader $upgrader WP_Upgrader instance. + * @param array $options Array of bulk item update data. + */ +function _gutenberg_get_global_stylesheet_clean_cache_upon_upgrading( $upgrader, $options ) { + if ( 'update' !== $options['action'] ) { + return; + } + + if ( + 'core' === $options['type'] || + 'plugin' === $options['type'] || + // Clean cache only if the active theme was updated. + ( 'theme' === $options['type'] && ( isset( $options['themes'][ get_stylesheet() ] ) || isset( $options['themes'][ get_template() ] ) ) ) + ) { + gutenberg_get_global_stylesheet_clean_cache(); + } +} + diff --git a/phpunit/wp-get-global-stylesheet-test.php b/phpunit/wp-get-global-stylesheet-test.php new file mode 100644 index 00000000000000..cb4d0242ce3e3a --- /dev/null +++ b/phpunit/wp-get-global-stylesheet-test.php @@ -0,0 +1,98 @@ +user->create( + array( + 'role' => 'administrator', + 'user_email' => 'administrator@example.com', + ) + ); + } + + public function set_up() { + parent::set_up(); + + $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; + $this->theme_root = realpath( DIR_TESTDATA . '/themedir1' ); + + // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. + $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); + + // Set up the new root. + add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); + + // Clear caches. + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + } + + public function tear_down() { + $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; + + // Clear up the filters to modify the theme root. + remove_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); + remove_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); + remove_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); + + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + + parent::tear_down(); + } + + public function filter_set_theme_root() { + return $this->theme_root; + } + + public function test_global_styles_user_cpt_change_invalidates_cached_stylesheet() { + add_filter( 'wp_get_global_stylesheet_can_use_cache', '__return_true' ); + switch_theme( 'block-theme' ); + wp_set_current_user( self::$administrator_id ); + + $styles = gutenberg_get_global_stylesheet(); + $this->assertStringNotContainsString( 'background-color: hotpink;', $styles ); + + $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( wp_get_theme(), true ); + $config = json_decode( $user_cpt['post_content'], true ); + $config['styles']['color']['background'] = 'hotpink'; + $user_cpt['post_content'] = wp_json_encode( $config ); + + wp_update_post( $user_cpt, true, false ); + + $styles = gutenberg_get_global_stylesheet(); + $this->assertStringContainsString( 'background-color: hotpink;', $styles ); + remove_filter( 'wp_get_global_stylesheet_can_use_cache', '__return_true' ); + } +} From 6088f5cfaaaee5ee9d378514af0e0e416b1a227e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:39:51 -0700 Subject: [PATCH 60/76] Update which origins are queried for `gutenberg_get_global_settings` (#45971) Co-authored-by: Felix Arntz --- .../get-global-styles-and-settings.php | 29 -------------- .../get-global-styles-and-settings.php | 38 +++++++++++++++++++ 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/lib/compat/wordpress-6.0/get-global-styles-and-settings.php b/lib/compat/wordpress-6.0/get-global-styles-and-settings.php index 9ed3e891182cb5..d4f1f9ef034924 100644 --- a/lib/compat/wordpress-6.0/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.0/get-global-styles-and-settings.php @@ -5,35 +5,6 @@ * @package gutenberg */ -/** - * Function to get the settings resulting of merging core, theme, and user data. - * - * @param array $path Path to the specific setting to retrieve. Optional. - * If empty, will return all settings. - * @param array $context { - * Metadata to know where to retrieve the $path from. Optional. - * - * @type string $block_name Which block to retrieve the settings from. - * If empty, it'll return the settings for the global context. - * @type string $origin Which origin to take data from. - * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). - * If empty or unknown, 'all' is used. - * } - * - * @return array The settings to retrieve. - */ -function gutenberg_get_global_settings( $path = array(), $context = array() ) { - if ( ! empty( $context['block_name'] ) ) { - $path = array_merge( array( 'blocks', $context['block_name'] ), $path ); - } - $origin = 'custom'; - if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) { - $origin = 'theme'; - } - $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_settings(); - return _wp_array_get( $settings, $path, $settings ); -} - /** * Function to get the styles resulting of merging core, theme, and user data. * diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index 5d37d3182ccc85..cee145acca5fc6 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -189,3 +189,41 @@ function _gutenberg_get_global_stylesheet_clean_cache_upon_upgrading( $upgrader, } } +/** + * Function to get the settings resulting of merging core, theme, and user data. + * + * @param array $path Path to the specific setting to retrieve. Optional. + * If empty, will return all settings. + * @param array $context { + * Metadata to know where to retrieve the $path from. Optional. + * + * @type string $block_name Which block to retrieve the settings from. + * If empty, it'll return the settings for the global context. + * @type string $origin Which origin to take data from. + * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). + * If empty or unknown, 'all' is used. + * } + * + * @return array The settings to retrieve. + */ +function gutenberg_get_global_settings( $path = array(), $context = array() ) { + if ( ! empty( $context['block_name'] ) ) { + $new_path = array( 'blocks', $context['block_name'] ); + foreach ( $path as $subpath ) { + $new_path[] = $subpath; + } + $path = $new_path; + } + + // This is the default value when no origin is provided or when it is 'all'. + $origin = 'custom'; + if ( + ! wp_theme_has_theme_json() || + ( isset( $context['origin'] ) && 'base' === $context['origin'] ) + ) { + $origin = 'theme'; + } + + $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_settings(); + return _wp_array_get( $settings, $path, $settings ); +} From 4183090b2030efce7f946f88cc3bdbb22ed6f9d5 Mon Sep 17 00:00:00 2001 From: Jonny Harris Date: Tue, 22 Nov 2022 20:30:38 +0000 Subject: [PATCH 61/76] Cleaner logic. (#45950) --- .../get-global-styles-and-settings.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index cee145acca5fc6..e74ef3144ef095 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -25,23 +25,23 @@ function wp_theme_has_theme_json() { * The reason not to store it as a boolean is to avoid working * with the $found parameter which apparently had some issues in some implementations * https://developer.wordpress.org/reference/functions/wp_cache_get/ + * + * Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. */ - if ( - // Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. - ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && - ( 0 === $theme_has_support || 1 === $theme_has_support ) - ) { + if ( ! WP_DEBUG && is_int( $theme_has_support ) ) { return (bool) $theme_has_support; } // Has the own theme a theme.json? - $theme_has_support = is_readable( get_stylesheet_directory() . '/theme.json' ) ? 1 : 0; + $theme_has_support = is_readable( get_stylesheet_directory() . '/theme.json' ); // Look up the parent if the child does not have a theme.json. - if ( 0 === $theme_has_support ) { - $theme_has_support = is_readable( get_template_directory() . '/theme.json' ) ? 1 : 0; + if ( ! $theme_has_support ) { + $theme_has_support = is_readable( get_template_directory() . '/theme.json' ); } + $theme_has_support = $theme_has_support ? 1 : 0; + wp_cache_set( $cache_key, $theme_has_support, $cache_group ); return (bool) $theme_has_support; From 7e76592a8fec0ee9e6a8dcb3cfda558fbc07ecec Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Tue, 15 Nov 2022 16:36:30 -0700 Subject: [PATCH 62/76] Tag Processor: Add ability to stop at tag closers, if requested `WP_HTML_Tag_Processor` introduces the ability to walk through an HTML document and modify attributes on tag openers. At times it could be useful to stop also at tag closers in order to perform augmented inspection and querying of a document. In this patch we're opening up a mode that allows just that; if querying with the `[ "tag_closers" => "visit" ]` query parrameter then in addition to stopping at tag openers, this will allow stopping at all tag tokens regardless of open or close status. --- .../html/class-wp-html-tag-processor.php | 137 +++++++++++++----- phpunit/html/wp-html-tag-processor-test.php | 56 +++++++ 2 files changed, 159 insertions(+), 34 deletions(-) diff --git a/lib/experimental/html/class-wp-html-tag-processor.php b/lib/experimental/html/class-wp-html-tag-processor.php index 6e72fc38ff4118..acdf8d97a95a96 100644 --- a/lib/experimental/html/class-wp-html-tag-processor.php +++ b/lib/experimental/html/class-wp-html-tag-processor.php @@ -221,6 +221,14 @@ class WP_HTML_Tag_Processor { */ private $sought_match_offset; + /** + * Whether to visit tag closers, e.g.
, when walking an input document. + * + * @since 6.2.0 + * @var boolean + */ + private $stop_on_tag_closers; + /** * The updated HTML document. * @@ -276,6 +284,29 @@ class WP_HTML_Tag_Processor { */ private $tag_name_length; + /** + * Byte offset in input document where current tag token ends. + * + * Example: + * ``` + *
... + * 0 1 | + * 01234567890123456 + * --- tag name ends at 14 + * ``` + * + * @since 6.2.0 + * @var ?int + */ + private $tag_ends_at; + + /** + * Whether the current tag is an opening tag, e.g.
, or a closing tag, e.g.
. + * + * @var boolean + */ + private $is_closing_tag; + /** * Lazily-built index of attributes found within an HTML tag, keyed by the attribute name. * @@ -412,7 +443,16 @@ public function next_tag( $query = null ) { return false; } - $this->parse_tag_opener_attributes(); + while ( $this->parse_next_attribute() ) { + continue; + } + + $tag_ends_at = strpos( $this->html, '>', $this->parsed_bytes ); + if ( false === $tag_ends_at ) { + return false; + } + $this->tag_ends_at = $tag_ends_at; + $this->parsed_bytes = $tag_ends_at; if ( $this->matches() ) { ++$already_found; @@ -495,7 +535,9 @@ private function skip_rcdata( $tag_name ) { continue; } - $this->skip_tag_closer_attributes(); + while ( $this->parse_next_attribute() ) { + continue; + } $at = $this->parsed_bytes; if ( $at >= strlen( $this->html ) ) { return false; @@ -620,12 +662,14 @@ private function skip_script_data() { if ( $is_closing ) { $this->parsed_bytes = $at; - $this->skip_tag_closer_attributes(); - if ( $this->parsed_bytes >= $doc_length ) { return false; } + while ( $this->parse_next_attribute() ) { + continue; + } + if ( '>' === $html[ $this->parsed_bytes ] ) { ++$this->parsed_bytes; return true; @@ -656,6 +700,13 @@ private function parse_next_tag() { return false; } + if ( '/' === $this->html[ $at + 1 ] ) { + $this->is_closing_tag = true; + $at++; + } else { + $this->is_closing_tag = false; + } + /* * HTML tag names must start with [a-zA-Z] otherwise they are not tags. * For example, "<3" is rendered as text, not a tag opener. This means @@ -777,35 +828,12 @@ private function parse_next_tag() { return false; } - /** - * Parses all attributes of the current tag. - * - * @since 6.2.0 - */ - private function parse_tag_opener_attributes() { - while ( $this->parse_next_attribute() ) { - continue; - } - } - - /** - * Skips all attributes of the current tag. - * - * @since 6.2.0 - */ - private function skip_tag_closer_attributes() { - while ( $this->parse_next_attribute( 'tag-closer' ) ) { - continue; - } - } - /** * Parses the next attribute. * - * @param string $context tag-opener or tag-closer. * @since 6.2.0 */ - private function parse_next_attribute( $context = 'tag-opener' ) { + private function parse_next_attribute() { // Skip whitespace and slashes. $this->parsed_bytes += strspn( $this->html, " \t\f\r\n/", $this->parsed_bytes ); if ( $this->parsed_bytes >= strlen( $this->html ) ) { @@ -872,7 +900,7 @@ private function parse_next_attribute( $context = 'tag-opener' ) { return false; } - if ( 'tag-opener' !== $context ) { + if ( $this->is_closing_tag ) { return true; } @@ -914,6 +942,8 @@ private function after_tag() { $this->apply_attributes_updates(); $this->tag_name_starts_at = null; $this->tag_name_length = null; + $this->tag_ends_at = null; + $this->is_closing_tag = null; $this->attributes = array(); } @@ -1159,6 +1189,25 @@ public function get_tag() { return strtoupper( $tag_name ); } + /** + * Indicates if the current tag token is a tag closer. + * + * Example: + * + * $p = new WP_HTML_Tag_Processor( '
' ); + * $p->next_tag( [ 'tag_name' => 'div', 'tag_closers' => 'visit' ] ); + * $p->is_tag_closer() === false; + * + * $p->next_tag( [ 'tag_name' => 'div', 'tag_closers' => 'visit' ] ); + * $p->is_tag_closer() === true; + *
+ * + * @return bool + */ + public function is_tag_closer() { + return $this->is_closing_tag; + } + /** * Updates or creates a new attribute on the currently matched tag with the value passed. * @@ -1175,8 +1224,8 @@ public function get_tag() { * @throws Exception When WP_DEBUG is true and the attribute name is invalid. */ public function set_attribute( $name, $value ) { - if ( null === $this->tag_name_starts_at ) { - return; + if ( $this->is_closing_tag || null === $this->tag_name_starts_at ) { + return false; } /* @@ -1286,8 +1335,8 @@ public function set_attribute( $name, $value ) { * @param string $name The attribute name to remove. */ public function remove_attribute( $name ) { - if ( ! isset( $this->attributes[ $name ] ) ) { - return; + if ( $this->is_closing_tag || ! isset( $this->attributes[ $name ] ) ) { + return false; } /* @@ -1316,6 +1365,10 @@ public function remove_attribute( $name ) { * @param string $class_name The class name to add. */ public function add_class( $class_name ) { + if ( $this->is_closing_tag ) { + return false; + } + if ( null !== $this->tag_name_starts_at ) { $this->classname_updates[ $class_name ] = self::ADD_CLASS; } @@ -1329,6 +1382,10 @@ public function add_class( $class_name ) { * @param string $class_name The class name to remove. */ public function remove_class( $class_name ) { + if ( $this->is_closing_tag ) { + return false; + } + if ( null !== $this->tag_name_starts_at ) { $this->classname_updates[ $class_name ] = self::REMOVE_CLASS; } @@ -1392,7 +1449,9 @@ public function get_updated_html() { // Parse the attributes in the updated markup. $this->attributes = array(); - $this->parse_tag_opener_attributes(); + while ( $this->parse_next_attribute() ) { + continue; + } return $this->html; } @@ -1407,6 +1466,7 @@ public function get_updated_html() { * * @type string|null $tag_name Which tag to find, or `null` for "any tag." * @type string|null $class_name Tag must contain this class name to match. + * @type string $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
. * } */ private function parse_query( $query ) { @@ -1418,6 +1478,7 @@ private function parse_query( $query ) { $this->sought_tag_name = null; $this->sought_class_name = null; $this->sought_match_offset = 1; + $this->stop_on_tag_closers = false; // A single string value means "find the tag of this name". if ( is_string( $query ) ) { @@ -1441,6 +1502,10 @@ private function parse_query( $query ) { if ( isset( $query['match_offset'] ) && is_int( $query['match_offset'] ) && 0 < $query['match_offset'] ) { $this->sought_match_offset = $query['match_offset']; } + + if ( isset( $query['tag_closers'] ) ) { + $this->stop_on_tag_closers = 'visit' === $query['tag_closers']; + } } @@ -1452,6 +1517,10 @@ private function parse_query( $query ) { * @return boolean */ private function matches() { + if ( $this->is_closing_tag && ! $this->stop_on_tag_closers ) { + return false; + } + // Do we match a case-insensitive HTML tag name? if ( null !== $this->sought_tag_name ) { /* diff --git a/phpunit/html/wp-html-tag-processor-test.php b/phpunit/html/wp-html-tag-processor-test.php index e66b1d50758d0b..273cbddddea4bf 100644 --- a/phpunit/html/wp-html-tag-processor-test.php +++ b/phpunit/html/wp-html-tag-processor-test.php @@ -237,6 +237,35 @@ public function test_next_tag_should_return_false_for_a_non_existing_tag() { $this->assertFalse( $p->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); } + /** + * @covers next_tag + * @covers is_tag_closer + */ + public function test_next_tag_should_stop_on_closers_only_when_requested() { + $p = new WP_HTML_Tag_Processor( '
' ); + $this->assertTrue( $p->next_tag( array( 'tag_name' => 'div' ) ), 'Did not find desired tag opener' ); + $this->assertFalse( $p->next_tag( array( 'tag_name' => 'div' ) ), 'Visited an unwanted tag, a tag closer' ); + + $p = new WP_HTML_Tag_Processor( '
' ); + $p->next_tag( + array( + 'tag_name' => 'div', + 'tag_closers' => 'visit', + ) + ); + $this->assertFalse( $p->is_tag_closer(), 'Indicated a tag opener is a tag closer' ); + $this->assertTrue( + $p->next_tag( + array( + 'tag_name' => 'div', + 'tag_closers' => 'visit', + ) + ), + 'Did not stop at desired tag closer' + ); + $this->assertTrue( $p->is_tag_closer(), 'Indicated a tag closer is a tag opener' ); + } + /** * @ticket 56299 * @@ -255,6 +284,33 @@ public function test_set_attribute_on_a_non_existing_tag_does_not_change_the_mar ); } + public function test_attribute_ops_on_tag_closer_do_not_change_the_markup() { + $p = new WP_HTML_Tag_Processor( '
' ); + $p->next_tag( + array( + 'tag_name' => 'div', + 'tag_closers' => 'visit', + ) + ); + $this->assertFalse( $p->is_tag_closer(), 'Skipped tag opener' ); + $p->next_tag( + array( + 'tag_name' => 'div', + 'tag_closers' => 'visit', + ) + ); + $this->assertTrue( $p->is_tag_closer(), 'Skipped tag closer' ); + $this->assertFalse( $p->set_attribute( 'id', 'test' ), "Allowed setting an attribute on a tag closer when it shouldn't have" ); + $this->assertFalse( $p->remove_attribute( 'invalid-id' ), "Allowed removing an attribute on a tag closer when it shouldn't have" ); + $this->assertFalse( $p->add_class( 'sneaky' ), "Allowed adding a class on a tag closer when it shouldn't have" ); + $this->assertFalse( $p->remove_class( 'not-appearing-in-this-test' ), "Allowed removing a class on a tag closer when it shouldn't have" ); + $this->assertSame( + '
', + $p->get_updated_html(), + 'Calling get_updated_html after updating a non-existing tag returned an HTML that was different from the original HTML' + ); + } + /** * Passing a double quote inside of an attribute values could lead to an XSS attack as follows: * From d98e24e94bd6208be85196d409c8e63c8db5627b Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 23 Nov 2022 18:59:44 +1300 Subject: [PATCH 63/76] Colorize template parts and Reusable blocks (#45473) --- packages/base-styles/_colors.scss | 7 ++++++ .../src/components/block-card/index.js | 12 +++++++++- .../src/components/block-card/style.scss | 4 ++++ .../block-content-overlay/style.scss | 8 ++++++- .../src/components/block-toolbar/index.js | 17 +++++++++---- .../src/components/block-toolbar/style.scss | 10 ++++++++ .../components/inserter-list-item/index.js | 12 +++++++++- .../components/inserter-list-item/style.scss | 5 ++++ .../src/components/list-view/block.js | 3 +++ .../src/components/list-view/branch.js | 7 ++++++ .../src/components/list-view/style.scss | 24 +++++++++++++++---- .../use-block-display-information/index.js | 19 +++++++++++---- packages/block-library/src/block/editor.scss | 17 +++++++++++++ .../src/template-part/editor.scss | 22 +++++++++++++++-- packages/blocks/src/api/registration.js | 2 +- .../document-actions/index.js | 23 +++++++++++------- .../document-actions/style.scss | 8 ++++++- .../components/template-details/style.scss | 4 ++++ 18 files changed, 173 insertions(+), 31 deletions(-) diff --git a/packages/base-styles/_colors.scss b/packages/base-styles/_colors.scss index 03f6bfbdd566d4..5847a66f8b2435 100644 --- a/packages/base-styles/_colors.scss +++ b/packages/base-styles/_colors.scss @@ -1,3 +1,5 @@ +@import "./functions"; + /** * Colors */ @@ -24,3 +26,8 @@ $light-gray-placeholder: rgba($white, 0.65); $alert-yellow: #f0b849; $alert-red: #cc1818; $alert-green: #4ab866; + +:root { + --wp-block-synced-color: #7a00df; + --wp-block-synced-color--rgb: #{hex-to-rgb(#7a00df)}; +} diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index 6362b1b4ca2c7c..afda93d5ece7af 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -17,6 +22,7 @@ function BlockCard( { blockType, parentBlockClientId, handleBackButton, + isSynced, } ) { if ( blockType ) { deprecated( '`blockType` property in `BlockCard component`', { @@ -30,7 +36,11 @@ function BlockCard( { window?.__experimentalEnableOffCanvasNavigationEditor === true; return ( -
+
{ isOffCanvasNavigationEditorEnabled && parentBlockClientId && (