From 9953bfc6065f395eaa7f8ee43bf3265b53362ef4 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Mon, 18 Oct 2021 14:18:02 +0100 Subject: [PATCH 01/28] FSE: Allow child theme.json to be merged with parent theme.json (#35459) * FSE: Allow child theme.json to be merged with parent theme.json * Update lib/class-wp-theme-json-resolver-gutenberg.php Co-authored-by: Ari Stathopoulos * add a claifying comment * Update lib/class-wp-theme-json-resolver-gutenberg.php Co-authored-by: George Mamadashvili * Update lib/class-wp-theme-json-resolver-gutenberg.php Co-authored-by: George Mamadashvili * Fix lint error * Add directory flag to get_file_path_from_theme method Only use get_theme_file_path in theme_has_support check * Add unit tests for merging Co-authored-by: Ari Stathopoulos Co-authored-by: George Mamadashvili --- ...class-wp-theme-json-resolver-gutenberg.php | 27 ++++++--- phpunit/class-wp-theme-json-resolver-test.php | 59 +++++++++++++++++++ phpunit/data/themedir1/fse-child/style.css | 8 +++ phpunit/data/themedir1/fse-child/theme.json | 51 ++++++++++++++++ 4 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 phpunit/data/themedir1/fse-child/style.css create mode 100644 phpunit/data/themedir1/fse-child/theme.json diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 7f5eb1f09527d6..c4d1dfcf9723c9 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -274,6 +274,18 @@ public static function get_theme_data( $theme_support_data = array() ) { $theme_json_data = self::read_json_file( self::get_file_path_from_theme( 'theme.json' ) ); $theme_json_data = self::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) ); self::$theme = new WP_Theme_JSON_Gutenberg( $theme_json_data ); + + if ( wp_get_theme()->parent() ) { + // Get parent theme.json. + $parent_theme_json_data = self::read_json_file( self::get_file_path_from_theme( 'theme.json', true ) ); + $parent_theme_json_data = self::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) ); + $parent_theme = new WP_Theme_JSON_Gutenberg( $parent_theme_json_data ); + + // Merge the child theme.json into the parent theme.json. + // The child theme takes precedence over the parent. + $parent_theme->merge( self::$theme ); + self::$theme = $parent_theme; + } } if ( empty( $theme_support_data ) ) { @@ -476,7 +488,7 @@ public static function get_user_custom_post_type_id() { */ public static function theme_has_support() { if ( ! isset( self::$theme_has_support ) ) { - self::$theme_has_support = (bool) self::get_file_path_from_theme( 'theme.json' ); + self::$theme_has_support = is_readable( get_theme_file_path( 'theme.json' ) ); } return self::$theme_has_support; @@ -490,15 +502,14 @@ public static function theme_has_support() { * otherwise returns the whole file path. * * @param string $file_name Name of the file. + * @param bool $template Use template theme directroy. Default: false. * @return string The whole file path or empty if the file doesn't exist. */ - private static function get_file_path_from_theme( $file_name ) { - $located = ''; - $candidate = get_theme_file_path( $file_name ); - if ( is_readable( $candidate ) ) { - $located = $candidate; - } - return $located; + private static function get_file_path_from_theme( $file_name, $template = false ) { + $path = $template ? get_template_directory() : get_stylesheet_directory(); + $candidate = $path . '/' . $file_name; + + return is_readable( $candidate ) ? $candidate : ''; } /** diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index b2441229d3590f..b3986d1b9551a6 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -255,4 +255,63 @@ function test_add_theme_supports_are_loaded_for_themes_without_theme_json() { $this->assertSame( $color_palette, $settings['color']['palette']['theme'] ); } + function test_merges_child_theme_json_into_parent_theme_json() { + switch_theme( 'fse-child' ); + + $actual = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data(); + + // Should merge settings. + $this->assertSame( + array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'light', + 'name' => 'Light', + 'color' => '#f3f4f6', + ), + array( + 'slug' => 'primary', + 'name' => 'Primary', + 'color' => '#3858e9', + ), + array( + 'slug' => 'dark', + 'name' => 'Dark', + 'color' => '#111827', + ), + ), + ), + 'custom' => true, + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'light', + 'name' => 'Light', + 'color' => '#f3f4f6', + ), + ), + ), + ), + ), + ), + ), + $actual->get_settings() + ); + + $this->assertSame( + $actual->get_custom_templates(), + array( + 'page-home' => array( + 'title' => 'Homepage', + 'postTypes' => array( 'page' ), + ), + ) + ); + } } diff --git a/phpunit/data/themedir1/fse-child/style.css b/phpunit/data/themedir1/fse-child/style.css new file mode 100644 index 00000000000000..ef9406d1d977dc --- /dev/null +++ b/phpunit/data/themedir1/fse-child/style.css @@ -0,0 +1,8 @@ +/* +Theme Name: FSE Child Theme +Theme URI: https://wordpress.org/ +Description: For testing purposes only. +Template: fse +Version: 1.0.0 +Text Domain: fse-child +*/ diff --git a/phpunit/data/themedir1/fse-child/theme.json b/phpunit/data/themedir1/fse-child/theme.json new file mode 100644 index 00000000000000..4e3f75c503fe52 --- /dev/null +++ b/phpunit/data/themedir1/fse-child/theme.json @@ -0,0 +1,51 @@ +{ + "version": 1, + "settings": { + "color": { + "palette": [ + { + "slug": "light", + "name": "Light", + "color": "#f3f4f6" + }, + { + "slug": "primary", + "name": "Primary", + "color": "#3858e9" + }, + { + "slug": "dark", + "name": "Dark", + "color": "#111827" + } + ], + "custom": true + }, + "blocks": { + "core/paragraph": { + "color": { + "palette": [ + { + "slug": "light", + "name": "Light", + "color": "#f3f4f6" + } + ] + } + } + } + }, + "customTemplates": [ + { + "name": "page-home", + "title": "Homepage" + } + ], + "templateParts": [ + { + "name": "small-header", + "title": "Small Header", + "area": "header" + } + ] +} From 3a8852d813fe7ba794c6e63f8e4945f3000c0eab Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Tue, 19 Oct 2021 04:47:16 +0900 Subject: [PATCH 02/28] Storybook: Enable Controls and disable Knobs by default (#35682) * Storybook: Add Controls * Disable Knobs by default * Enable Knobs where currently used * Update contributor docs * Revert TOC --- package-lock.json | 333 ++++++++++++++++++ package.json | 1 + packages/components/CONTRIBUTING.md | 22 +- .../alignment-matrix-control/stories/index.js | 3 + .../src/base-control/stories/index.js | 8 +- .../components/src/button/stories/index.js | 8 +- packages/components/src/card/stories/index.js | 3 + .../src/checkbox-control/stories/index.js | 3 + .../src/color-indicator/stories/index.js | 3 + .../src/color-palette/stories/index.js | 8 +- .../src/color-picker/stories/index.js | 3 + .../components/src/date-time/stories/index.js | 3 + .../components/src/date-time/stories/time.js | 8 +- .../components/src/divider/stories/index.js | 3 + .../src/dropdown-menu/stories/index.js | 8 +- .../components/src/elevation/stories/index.js | 3 + .../src/external-link/stories/index.js | 8 +- .../src/font-size-picker/stories/index.js | 3 + .../src/form-token-field/stories/index.js | 3 + .../src/gradient-picker/stories/index.js | 3 + packages/components/src/grid/stories/index.js | 3 + .../components/src/guide/stories/index.js | 8 +- packages/components/src/icon/stories/index.js | 8 +- .../src/input-control/stories/index.js | 3 + .../src/item-group/stories/index.js | 3 + .../components/src/modal/stories/index.js | 8 +- .../components/src/notice/stories/index.js | 3 + .../src/number-control/stories/index.js | 3 + .../components/src/panel/stories/index.js | 8 +- .../src/placeholder/stories/index.js | 8 +- .../components/src/popover/stories/index.js | 8 +- .../src/range-control/stories/index.js | 8 +- .../src/resizable-box/stories/index.js | 8 +- .../src/scrollable/stories/index.js | 3 + .../src/search-control/stories/index.js | 3 + .../src/select-control/stories/index.js | 3 + .../components/src/slot-fill/stories/index.js | 3 + .../components/src/snackbar/stories/index.js | 8 +- .../components/src/spacer/stories/index.js | 3 + .../components/src/surface/stories/index.js | 3 + .../components/src/tab-panel/stories/index.js | 8 +- .../src/text-control/stories/index.js | 3 + .../src/text-highlight/stories/index.js | 8 +- .../src/textarea-control/stories/index.js | 3 + packages/components/src/tip/stories/index.js | 8 +- .../src/toggle-control/stories/index.js | 8 +- .../src/toggle-group-control/stories/index.js | 3 + .../src/toolbar-button/stories/index.js | 8 +- .../components/src/tooltip/stories/index.js | 3 + .../src/tree-select/stories/index.js | 3 + .../components/src/truncate/stories/index.js | 3 + .../src/unit-control/stories/index.js | 3 + .../components/src/z-stack/stories/index.js | 3 + storybook/main.js | 3 +- storybook/preview.js | 8 + 55 files changed, 590 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 97de983112cf1b..235b750396072f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7995,6 +7995,339 @@ } } }, + "@storybook/addon-controls": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-6.3.12.tgz", + "integrity": "sha512-WO/PbygE4sDg3BbstJ49q0uM3Xu5Nw4lnHR5N4hXSvRAulZt1d1nhphRTHjfX+CW+uBcfzkq9bksm6nKuwmOyw==", + "dev": true, + "requires": { + "@storybook/addons": "6.3.12", + "@storybook/api": "6.3.12", + "@storybook/client-api": "6.3.12", + "@storybook/components": "6.3.12", + "@storybook/node-logger": "6.3.12", + "@storybook/theming": "6.3.12", + "core-js": "^3.8.2", + "ts-dedent": "^2.0.0" + }, + "dependencies": { + "@emotion/styled": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-10.0.27.tgz", + "integrity": "sha512-iK/8Sh7+NLJzyp9a5+vIQIXTYxfT4yB/OJbjzQanB2RZpvmzBQOHZWhpAMZWYEKRNNbsD6WfBw5sVWkb6WzS/Q==", + "dev": true, + "requires": { + "@emotion/styled-base": "^10.0.27", + "babel-plugin-emotion": "^10.0.27" + } + }, + "@storybook/addons": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.3.12.tgz", + "integrity": "sha512-UgoMyr7Qr0FS3ezt8u6hMEcHgyynQS9ucr5mAwZky3wpXRPFyUTmMto9r4BBUdqyUvTUj/LRKIcmLBfj+/l0Fg==", + "dev": true, + "requires": { + "@storybook/api": "6.3.12", + "@storybook/channels": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/core-events": "6.3.12", + "@storybook/router": "6.3.12", + "@storybook/theming": "6.3.12", + "core-js": "^3.8.2", + "global": "^4.4.0", + "regenerator-runtime": "^0.13.7" + } + }, + "@storybook/api": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.3.12.tgz", + "integrity": "sha512-LScRXUeCWEW/OP+jiooNMQICVdusv7azTmULxtm72fhkXFRiQs2CdRNTiqNg46JLLC9z95f1W+pGK66X6HiiQA==", + "dev": true, + "requires": { + "@reach/router": "^1.3.4", + "@storybook/channels": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/core-events": "6.3.12", + "@storybook/csf": "0.0.1", + "@storybook/router": "6.3.12", + "@storybook/semver": "^7.3.2", + "@storybook/theming": "6.3.12", + "@types/reach__router": "^1.3.7", + "core-js": "^3.8.2", + "fast-deep-equal": "^3.1.3", + "global": "^4.4.0", + "lodash": "^4.17.20", + "memoizerific": "^1.11.3", + "qs": "^6.10.0", + "regenerator-runtime": "^0.13.7", + "store2": "^2.12.0", + "telejson": "^5.3.2", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + } + }, + "@storybook/channel-postmessage": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.3.12.tgz", + "integrity": "sha512-Ou/2Ga3JRTZ/4sSv7ikMgUgLTeZMsXXWLXuscz4oaYhmOqAU9CrJw0G1NitwBgK/+qC83lEFSLujHkWcoQDOKg==", + "dev": true, + "requires": { + "@storybook/channels": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/core-events": "6.3.12", + "core-js": "^3.8.2", + "global": "^4.4.0", + "qs": "^6.10.0", + "telejson": "^5.3.2" + } + }, + "@storybook/channels": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.3.12.tgz", + "integrity": "sha512-l4sA+g1PdUV8YCbgs47fIKREdEQAKNdQIZw0b7BfTvY9t0x5yfBywgQhYON/lIeiNGz2OlIuD+VUtqYfCtNSyw==", + "dev": true, + "requires": { + "core-js": "^3.8.2", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + } + }, + "@storybook/client-api": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.3.12.tgz", + "integrity": "sha512-xnW+lKKK2T774z+rOr9Wopt1aYTStfb86PSs9p3Fpnc2Btcftln+C3NtiHZl8Ccqft8Mz/chLGgewRui6tNI8g==", + "dev": true, + "requires": { + "@storybook/addons": "6.3.12", + "@storybook/channel-postmessage": "6.3.12", + "@storybook/channels": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/core-events": "6.3.12", + "@storybook/csf": "0.0.1", + "@types/qs": "^6.9.5", + "@types/webpack-env": "^1.16.0", + "core-js": "^3.8.2", + "global": "^4.4.0", + "lodash": "^4.17.20", + "memoizerific": "^1.11.3", + "qs": "^6.10.0", + "regenerator-runtime": "^0.13.7", + "stable": "^0.1.8", + "store2": "^2.12.0", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + } + }, + "@storybook/client-logger": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.3.12.tgz", + "integrity": "sha512-zNDsamZvHnuqLznDdP9dUeGgQ9TyFh4ray3t1VGO7ZqWVZ2xtVCCXjDvMnOXI2ifMpX5UsrOvshIPeE9fMBmiQ==", + "dev": true, + "requires": { + "core-js": "^3.8.2", + "global": "^4.4.0" + } + }, + "@storybook/components": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.3.12.tgz", + "integrity": "sha512-kdQt8toUjynYAxDLrJzuG7YSNL6as1wJoyzNUaCfG06YPhvIAlKo7le9tS2mThVFN5e9nbKrW3N1V1sp6ypZXQ==", + "dev": true, + "requires": { + "@popperjs/core": "^2.6.0", + "@storybook/client-logger": "6.3.12", + "@storybook/csf": "0.0.1", + "@storybook/theming": "6.3.12", + "@types/color-convert": "^2.0.0", + "@types/overlayscrollbars": "^1.12.0", + "@types/react-syntax-highlighter": "11.0.5", + "color-convert": "^2.0.1", + "core-js": "^3.8.2", + "fast-deep-equal": "^3.1.3", + "global": "^4.4.0", + "lodash": "^4.17.20", + "markdown-to-jsx": "^7.1.3", + "memoizerific": "^1.11.3", + "overlayscrollbars": "^1.13.1", + "polished": "^4.0.5", + "prop-types": "^15.7.2", + "react-colorful": "^5.1.2", + "react-popper-tooltip": "^3.1.1", + "react-syntax-highlighter": "^13.5.3", + "react-textarea-autosize": "^8.3.0", + "regenerator-runtime": "^0.13.7", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + } + }, + "@storybook/core-events": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.3.12.tgz", + "integrity": "sha512-SXfD7xUUMazaeFkB92qOTUV8Y/RghE4SkEYe5slAdjeocSaH7Nz2WV0rqNEgChg0AQc+JUI66no8L9g0+lw4Gw==", + "dev": true, + "requires": { + "core-js": "^3.8.2" + } + }, + "@storybook/node-logger": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.12.tgz", + "integrity": "sha512-iktOem/Ls2+dsZY9PhPeC6T1QhX/y7OInP88neLsqEPEbB2UXca3Ydv7OZBhBVbvN25W45b05MRzbtNUxYLNRw==", + "dev": true, + "requires": { + "@types/npmlog": "^4.1.2", + "chalk": "^4.1.0", + "core-js": "^3.8.2", + "npmlog": "^4.1.2", + "pretty-hrtime": "^1.0.3" + } + }, + "@storybook/router": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.3.12.tgz", + "integrity": "sha512-G/pNGCnrJRetCwyEZulHPT+YOcqEj/vkPVDTUfii2qgqukup6K0cjwgd7IukAURnAnnzTi1gmgFuEKUi8GE/KA==", + "dev": true, + "requires": { + "@reach/router": "^1.3.4", + "@storybook/client-logger": "6.3.12", + "@types/reach__router": "^1.3.7", + "core-js": "^3.8.2", + "fast-deep-equal": "^3.1.3", + "global": "^4.4.0", + "lodash": "^4.17.20", + "memoizerific": "^1.11.3", + "qs": "^6.10.0", + "ts-dedent": "^2.0.0" + } + }, + "@storybook/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "dev": true, + "requires": { + "core-js": "^3.6.5", + "find-up": "^4.1.0" + } + }, + "@storybook/theming": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.3.12.tgz", + "integrity": "sha512-wOJdTEa/VFyFB2UyoqyYGaZdym6EN7RALuQOAMT6zHA282FBmKw8nL5DETHEbctpnHdcrMC/391teK4nNSrdOA==", + "dev": true, + "requires": { + "@emotion/core": "^10.1.1", + "@emotion/is-prop-valid": "^0.8.6", + "@emotion/styled": "^10.0.27", + "@storybook/client-logger": "6.3.12", + "core-js": "^3.8.2", + "deep-object-diff": "^1.1.0", + "emotion-theming": "^10.0.27", + "global": "^4.4.0", + "memoizerific": "^1.11.3", + "polished": "^4.0.5", + "resolve-from": "^5.0.0", + "ts-dedent": "^2.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + } + } + }, "@storybook/addon-docs": { "version": "6.3.2", "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-6.3.2.tgz", diff --git a/package.json b/package.json index 9b67f738af3189..56d6ec47031de7 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "@octokit/rest": "16.26.0", "@octokit/webhooks": "7.1.0", "@storybook/addon-a11y": "6.3.2", + "@storybook/addon-controls": "6.3.12", "@storybook/addon-docs": "6.3.2", "@storybook/addon-knobs": "6.2.9", "@storybook/addon-storysource": "6.3.2", diff --git a/packages/components/CONTRIBUTING.md b/packages/components/CONTRIBUTING.md index 088f56f43d6bb4..f5cda274bb7610 100644 --- a/packages/components/CONTRIBUTING.md +++ b/packages/components/CONTRIBUTING.md @@ -277,14 +277,24 @@ import Button from '../'; export default { title: 'Components/Button', component: Button }; -export const _default = () => ; - -export const primary = () => ; - -export const secondary = () => ; +const Template = ( args ) => diff --git a/packages/block-library/src/template-part/edit/placeholder/patterns-setup.js b/packages/block-library/src/template-part/edit/placeholder/patterns-setup.js index fc4f95e89756b8..bd28c4679fb313 100644 --- a/packages/block-library/src/template-part/edit/placeholder/patterns-setup.js +++ b/packages/block-library/src/template-part/edit/placeholder/patterns-setup.js @@ -62,7 +62,8 @@ export default function PatternsSetup( { { isTitleStep && ( diff --git a/packages/block-library/src/template-part/edit/selection/template-part-previews.js b/packages/block-library/src/template-part/edit/selection/template-part-previews.js index 83d6f94341930f..612629358d8b80 100644 --- a/packages/block-library/src/template-part/edit/selection/template-part-previews.js +++ b/packages/block-library/src/template-part/edit/selection/template-part-previews.js @@ -149,7 +149,9 @@ function TemplatePartsByArea( { > { sprintf( // Translators: %s for the template part variation ("Header", "Footer", "Template Part"). - 'There is no other %s available. If you are looking for another type of template part, try searching for it using the input above.', + __( + 'There is no other %s available. If you are looking for another type of template part, try searching for it using the input above.' + ), area && area !== 'uncategorized' ? labelsByArea[ area ] || area : __( 'Template Part' ) From 556248227df7c6d0fdaab6bdf9f5ff74590fb2b0 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 19 Oct 2021 17:09:10 +0800 Subject: [PATCH 08/28] Fix missing titles in general areas (#35657) * Fix general areas don't have names * Switch to the new template part icon --- packages/block-library/src/template-part/variations.js | 4 ++-- .../navigation-sidebar/navigation-panel/constants.js | 3 ++- .../template-part-converter/convert-to-template-part.js | 7 ++++++- packages/edit-site/src/store/constants.js | 2 ++ packages/editor/src/utils/get-template-part-icon.js | 4 ++-- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/template-part/variations.js b/packages/block-library/src/template-part/variations.js index da41d03f22339d..ca862f5993ccc6 100644 --- a/packages/block-library/src/template-part/variations.js +++ b/packages/block-library/src/template-part/variations.js @@ -7,7 +7,7 @@ import { header as headerIcon, footer as footerIcon, sidebar as sidebarIcon, - layout as layoutIcon, + symbolFilled as symbolFilledIcon, } from '@wordpress/icons'; /** @@ -23,7 +23,7 @@ function getTemplatePartIcon( iconName ) { } else if ( 'sidebar' === iconName ) { return sidebarIcon; } - return layoutIcon; + return symbolFilledIcon; } export function enhanceTemplatePartVariations( settings, name ) { diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/constants.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/constants.js index ae97bb567f473c..f601df39d61397 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/constants.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/constants.js @@ -10,6 +10,7 @@ import { TEMPLATE_PART_AREA_HEADER, TEMPLATE_PART_AREA_FOOTER, TEMPLATE_PART_AREA_SIDEBAR, + TEMPLATE_PART_AREA_GENERAL, } from '../../../store/constants'; export const TEMPLATES_PRIMARY = [ @@ -101,7 +102,7 @@ export const TEMPLATE_PARTS_SUB_MENUS = [ title: __( 'sidebars' ), }, { - area: 'uncategorized', + area: TEMPLATE_PART_AREA_GENERAL, menu: MENU_TEMPLATE_PARTS_GENERAL, title: __( 'general' ), }, diff --git a/packages/edit-site/src/components/template-part-converter/convert-to-template-part.js b/packages/edit-site/src/components/template-part-converter/convert-to-template-part.js index c43bb4d77640b1..7274344816f1f0 100644 --- a/packages/edit-site/src/components/template-part-converter/convert-to-template-part.js +++ b/packages/edit-site/src/components/template-part-converter/convert-to-template-part.js @@ -33,6 +33,11 @@ import { store as noticesStore } from '@wordpress/notices'; import { store as editorStore } from '@wordpress/editor'; import { check } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import { TEMPLATE_PART_AREA_GENERAL } from '../../store/constants'; + export default function ConvertToTemplatePart( { clientIds, blocks } ) { const instanceId = useInstanceId( ConvertToTemplatePart ); const [ isModalOpen, setIsModalOpen ] = useState( false ); @@ -40,7 +45,7 @@ export default function ConvertToTemplatePart( { clientIds, blocks } ) { const { replaceBlocks } = useDispatch( blockEditorStore ); const { saveEntityRecord } = useDispatch( coreStore ); const { createSuccessNotice } = useDispatch( noticesStore ); - const [ area, setArea ] = useState( 'uncategorized' ); + const [ area, setArea ] = useState( TEMPLATE_PART_AREA_GENERAL ); const templatePartAreas = useSelect( ( select ) => diff --git a/packages/edit-site/src/store/constants.js b/packages/edit-site/src/store/constants.js index ca1d0a659a80b7..0d876e77bf2231 100644 --- a/packages/edit-site/src/store/constants.js +++ b/packages/edit-site/src/store/constants.js @@ -13,9 +13,11 @@ export const STORE_NAME = 'core/edit-site'; export const TEMPLATE_PART_AREA_HEADER = 'header'; export const TEMPLATE_PART_AREA_FOOTER = 'footer'; export const TEMPLATE_PART_AREA_SIDEBAR = 'sidebar'; +export const TEMPLATE_PART_AREA_GENERAL = 'uncategorized'; export const TEMPLATE_PART_AREA_TO_NAME = { [ TEMPLATE_PART_AREA_HEADER ]: __( 'Header' ), [ TEMPLATE_PART_AREA_FOOTER ]: __( 'Footer' ), [ TEMPLATE_PART_AREA_SIDEBAR ]: __( 'Sidebar' ), + [ TEMPLATE_PART_AREA_GENERAL ]: __( 'General' ), }; diff --git a/packages/editor/src/utils/get-template-part-icon.js b/packages/editor/src/utils/get-template-part-icon.js index 1e5c052b0e35de..6c37fc373fcfc3 100644 --- a/packages/editor/src/utils/get-template-part-icon.js +++ b/packages/editor/src/utils/get-template-part-icon.js @@ -5,7 +5,7 @@ import { header as headerIcon, footer as footerIcon, sidebar as sidebarIcon, - layout as layoutIcon, + symbolFilled as symbolFilledIcon, } from '@wordpress/icons'; /** * Helper function to retrieve the corresponding icon by name. @@ -22,5 +22,5 @@ export function getTemplatePartIcon( iconName ) { } else if ( 'sidebar' === iconName ) { return sidebarIcon; } - return layoutIcon; + return symbolFilledIcon; } From 5593e1b8764251fa3786851c22da7bf08a507e1d Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Tue, 19 Oct 2021 11:19:52 +0200 Subject: [PATCH 09/28] Try: Remove color inheritance specificity. (#35725) * Try: Remove color inheritance specificity. * Try to target class instead of element. --- packages/block-library/src/navigation/style.scss | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index b0911b054d784c..9ee5f4548c3e6a 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -41,12 +41,14 @@ } // Menu item link. - // By targetting the markup directly we enable greater global styles compatibility. - // The extra container specificity is due to global styles outputting link styles that need overriding. - &.wp-block-navigation a { - // Inherit colors set by the block color definition. + // By adding low specificity, we enable compatibility with link colors set in theme.json, + // but still allow them to be overridden by user-set colors. + .wp-block-navigation-item__content { color: inherit; display: block; + + // Set the default menu item padding to zero, to allow text-only buttons. + padding: 0; } // Force links to inherit text decoration applied to navigation block. @@ -252,11 +254,6 @@ // That way if padding is set in theme.json, it still wins. // https://css-tricks.com/almanac/selectors/w/where/ -// Set the default menu item padding to zero, to allow text-only buttons. -.wp-block-navigation .wp-block-navigation-item__content { - padding: 0; -} - // When the menu has a background, items have paddings, reduce margins to compensate. // Treat margins and paddings differently when the block has a background. .wp-block-navigation:where(.has-background) a { From 2d7fd1ba80da3fa9e6af427ba014958b0cd608a8 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 19 Oct 2021 13:09:03 +0200 Subject: [PATCH 10/28] Build Workflow: Revert version bump if build job fails (#33239) In our plugin build workflow, which, when manually triggered, also contains a version bump step, and a step to create a release draft with the plugin build attached as an asset, the build step sometimes fails (e.g. because of npm flakiness) ([see e.g.](https://github.com/WordPress/gutenberg/runs/2951792952?check_suite_focus=true)). It's natural for people to try and re-trigger the step. However, this is also bound to fail, since at that point, the release branch has already been created, and the plugin version has already been bumped ([see e.g.](https://github.com/WordPress/gutenberg/runs/2952331107?check_suite_focus=true)). To prevent this, we can add another job to the workflow that upon build failure, reverts the version bump commit, and, if necessary, also deletes the release branch. See this thread for more background, and potential alternative approaches: https://github.com/WordPress/gutenberg/pull/33105/files#r664591278 Co-authored-by: Dave Smith --- .github/workflows/build-plugin-zip.yml | 53 ++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml index df11d86f673f88..0f40c8fce11d68 100644 --- a/.github/workflows/build-plugin-zip.yml +++ b/.github/workflows/build-plugin-zip.yml @@ -64,6 +64,8 @@ jobs: old_version: ${{ steps.get_version.outputs.old_version }} new_version: ${{ steps.get_version.outputs.new_version }} release_branch: ${{ steps.get_version.outputs.release_branch }} + release_branch_commit: ${{ steps.commit_version_bump_to_release_branch.outputs.version_bump_commit }} + trunk_commit: ${{ steps.commit_version_bump_to_trunk.outputs.version_bump_commit }} steps: - name: Checkout code @@ -125,16 +127,19 @@ jobs: sed -i "s/${{ steps.get_version.outputs.old_version }}/${VERSION}/g" readme.txt - name: Commit the version bump to the release branch + id: commit_version_bump_to_release_branch run: | git add gutenberg.php package.json package-lock.json readme.txt git commit -m "Bump plugin version to ${{ steps.get_version.outputs.new_version }}" git push --set-upstream origin "${{ steps.get_version.outputs.release_branch }}" + echo "::set-output name=version_bump_commit::$(git rev-parse --verify --short HEAD)" - name: Fetch trunk if: ${{ github.ref != 'refs/heads/trunk' }} run: git fetch --depth=1 origin trunk - name: Cherry-pick the version bump commit to trunk + id: commit_version_bump_to_trunk run: | git checkout trunk git pull @@ -142,6 +147,7 @@ jobs: if [[ ${{ steps.get_version.outputs.old_version }} == "$TRUNK_VERSION" ]]; then git cherry-pick "${{ steps.get_version.outputs.release_branch }}" git push + echo "::set-output name=version_bump_commit::$(git rev-parse --verify --short HEAD)" fi build: @@ -154,6 +160,8 @@ jobs: github.event_name == 'workflow_dispatch' || github.repository == 'WordPress/gutenberg' ) + outputs: + job_status: ${{ job.status }} steps: - name: Checkout code @@ -201,6 +209,51 @@ jobs: name: release-notes path: ./release-notes.txt + revert-version-bump: + name: Revert version bump if build failed + needs: [bump-version, build] + runs-on: ubuntu-latest + if: | + always() && + ( needs.build.outputs.job_status == 'failure' ) && + needs.bump-version.outputs.release_branch_commit + + steps: + - name: Checkout code + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 + with: + fetch-depth: 2 + ref: ${{ needs.bump-version.outputs.release_branch }} + token: ${{ secrets.GUTENBERG_TOKEN }} + + - name: Configure git user name and email + run: | + git config user.name "Gutenberg Repository Automation" + git config user.email gutenberg@wordpress.org + + - name: Revert version bump commit on release branch + if: | + github.event.inputs.version == 'stable' || + contains( needs.bump-version.outputs.old_version, 'rc' ) + run: | + git revert --no-edit ${{ needs.bump-version.outputs.release_branch_commit }} + git push --set-upstream origin "${{ needs.bump-version.outputs.release_branch }}" + + - name: Delete release branch if it was only just created for the RC + if: | + github.event.inputs.version == 'rc' && + ! contains( needs.bump-version.outputs.old_version, 'rc' ) + run: | + git push origin :"${{ needs.bump-version.outputs.release_branch }}" + + - name: Revert version bump on trunk + if: ${{ needs.bump-version.outputs.trunk_commit }} + run: | + git fetch --depth=2 origin trunk + git checkout trunk + git revert --no-edit ${{ needs.bump-version.outputs.trunk_commit }} + git push --set-upstream origin trunk + create-release: name: Create Release Draft and Attach Asset needs: [bump-version, build] From ecd87cab6bed242f385880312737883e2d285da7 Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Tue, 19 Oct 2021 13:51:12 +0200 Subject: [PATCH 11/28] [Mobile] - Update Xcode version to 12.5 (#35754) * Mobile - Update CI Xcode version to 12.5 * Mobile - Update Xcode to 12.5.1 --- .github/workflows/rnmobile-ios-runner.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index 4de60b55130c95..023465359c0fff 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -18,7 +18,7 @@ jobs: if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} strategy: matrix: - xcode: [12.2] + xcode: [12.5.1] native-test-name: [gutenberg-editor-initial-html] node: ['14'] From 277fee87c6fa768ee8807c828e2c0fb1852d7e08 Mon Sep 17 00:00:00 2001 From: Anton Vlasenko <43744263+anton-vlasenko@users.noreply.github.com> Date: Tue, 19 Oct 2021 15:45:23 +0200 Subject: [PATCH 12/28] Navigation: preload more API requests (#35402) * Add preloading for /__experimental/menus endpoint. * Add a new filter to allow to modify the data that has already been preloaded. * Commit WIP. * Preload more endpoints. * Fix preload paths. * Don't add slash if the key is empty. Otherwise we will get urls like /wp/v2/types/?context=edit (trailing slash). * Preload /wp/v2/types URLs. * Commit WIP. * 1. Fix the test. 2. Add PHPDOC blocks. 3. Remove {$editor_name}_preloaded_data filter. We are not going to use it. * Fix indents. * Add PHPDOC blocks to the public functions introduced in this PR. * Add @since tags. * OPTIONS type requests should be cached the same way as GET type requets. We must allow using cached data only once (to avoid using outdated data). * Fix the code style. * Use WP function build_query instead of http_build_query. * Fix code style. * Format the code. * Add a new filter to modify the preloaded data. This can be handy in many situations when we need to change the preloaded data. * Wire up createMenuPreloadingMiddleware so that we can preload menus data separately. * Fix PHPDOC blocks. * Add PHPDOC block. * Implement menu preloading middleware. * Remove the gutenberg_navigation_get_menu_endpoint function. It's not used. * We should only load data once. * Add PHPDOC block. * Fix the unit test. * Improve test code coverage. Add a new unit test for test_gutenberg_navigation_editor_preload_menus_function_returns_correct_data * Update lib/editor-settings.php Co-authored-by: Pascal Birchler * Fix code style. * Fix code style. * Fix code style. * Fix bug in createMenuPreloadingMiddleware. We should send correct response for /__experimental/menu/ endpoint. * Revert this change as it needs to be moved into a separate PR. * Add public access modifier to the functions. * Fix version name. * Update packages/edit-navigation/src/utils/index.js Co-authored-by: Adam Zielinski * Remove the yoda condition as it can be harder to read it for some people. * 1. Format the code. 2. Don't use variable filter name. Pass the ditor_name as the second parameter. * 1. Update PHPDOC block. 2. Remove redundant code. * Update lib/navigation-page.php Co-authored-by: Jonny Harris * Fix the comment. * Make the middleware unstable. We don't want it to become a stable API for now. See https://github.com/WordPress/gutenberg/pull/35402#discussion_r728636852 Co-authored-by: Pascal Birchler Co-authored-by: Adam Zielinski Co-authored-by: Jonny Harris --- lib/editor-settings.php | 12 ++ lib/navigation-page.php | 105 ++++++++++++++++- packages/core-data/src/resolvers.js | 2 +- packages/edit-navigation/src/index.js | 2 + packages/edit-navigation/src/utils/index.js | 122 ++++++++++++++++++++ phpunit/navigation-page-test.php | 85 ++++++++++++++ 6 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 packages/edit-navigation/src/utils/index.js create mode 100644 phpunit/navigation-page-test.php diff --git a/lib/editor-settings.php b/lib/editor-settings.php index 7e09cd52e6e228..db5ae81802815e 100644 --- a/lib/editor-settings.php +++ b/lib/editor-settings.php @@ -75,6 +75,18 @@ function gutenberg_initialize_editor( $editor_name, $editor_script_handle, $sett 'rest_preload_api_request', array() ); + + /** + * Filters the array of data that has been preloaded. + * + * The dynamic portion of the hook name, `$editor_name`, refers to the type of block editor. + * + * @param array $preload_data Array containing the preloaded data. + * @param string $editor_name Current editor name. + * @param array Array containing the filtered preloaded data. + */ + $preload_data = apply_filters( 'block_editor_preload_data', $preload_data, $editor_name ); + wp_add_inline_script( 'wp-api-fetch', sprintf( diff --git a/lib/navigation-page.php b/lib/navigation-page.php index 568fca8cb3bbfa..959c5d73864760 100644 --- a/lib/navigation-page.php +++ b/lib/navigation-page.php @@ -21,23 +21,84 @@ class="edit-navigation" } /** - * Initialize the Gutenberg Navigation page. + * This function returns an url for the /__experimental/menus endpoint * - * @since 7.8.0 + * @since 11.8.0 + * + * @param int $results_per_page Results per page. + * @return string + */ +function gutenberg_navigation_get_menus_endpoint( $results_per_page = 100 ) { + return '/__experimental/menus?' . build_query( + array( + 'per_page' => $results_per_page, + 'context' => 'edit', + '_locale' => 'user', + ) + ); +} + +/** + * This function returns an url for the /__experimental/menu-items endpoint + * + * @since 11.8.0 + * + * @param int $menu_id Menu ID. + * @param int $results_per_page Results per page. + * @return string + */ +function gutenberg_navigation_get_menu_items_endpoint( $menu_id, $results_per_page = 100 ) { + return '/__experimental/menu-items?' . build_query( + array( + 'context' => 'edit', + 'menus' => $menu_id, + 'per_page' => $results_per_page, + '_locale' => 'user', + ) + ); +} + +/** + * This function returns an url for the /wp/v2/types endpoint + * + * @since 11.8.0 + * + * @return string + */ +function gutenberg_navigation_get_types_endpoint() { + return '/wp/v2/types?' . build_query( + array( + 'context' => 'edit', + ) + ); +} + +/** + * Initialize the Gutenberg Navigation page. * * @param string $hook Page. + * @since 7.8.0 */ function gutenberg_navigation_init( $hook ) { if ( 'gutenberg_page_gutenberg-navigation' !== $hook ) { - return; + return; } + $menus = wp_get_nav_menus(); + $first_menu_id = ! empty( $menus ) ? $menus[0]->term_id : null; + $preload_paths = array( '/__experimental/menu-locations', array( '/wp/v2/pages', 'OPTIONS' ), array( '/wp/v2/posts', 'OPTIONS' ), + gutenberg_navigation_get_menus_endpoint(), + gutenberg_navigation_get_types_endpoint(), ); + if ( $first_menu_id ) { + $preload_paths[] = gutenberg_navigation_get_menu_items_endpoint( $first_menu_id ); + } + $settings = array_merge( gutenberg_get_default_block_editor_settings(), array( @@ -82,3 +143,41 @@ function gutenberg_navigation_editor_load_block_editor_scripts_and_styles( $is_b } add_filter( 'should_load_block_editor_scripts_and_styles', 'gutenberg_navigation_editor_load_block_editor_scripts_and_styles' ); + +/** + * This function removes menu-related data from the "common" preloading middleware and calls + * createMenuPreloadingMiddleware middleware because we need to use custom preloading logic for menus. + * + * @param Array $preload_data Array containing the preloaded data. + * @param string $context Current editor name. + * @return array Filtered preload data. + */ +function gutenberg_navigation_editor_preload_menus( $preload_data, $context ) { + if ( 'navigation_editor' !== $context ) { + return $preload_data; + } + + $menus_data_path = gutenberg_navigation_get_menus_endpoint(); + $menus_data = array(); + if ( ! empty( $preload_data[ $menus_data_path ] ) ) { + $menus_data = array( $menus_data_path => $preload_data[ $menus_data_path ] ); + } + + if ( ! $menus_data ) { + return $preload_data; + } + + wp_add_inline_script( + 'wp-edit-navigation', + sprintf( + 'wp.apiFetch.use( wp.editNavigation.__unstableCreateMenuPreloadingMiddleware( %s ) );', + wp_json_encode( $menus_data ) + ), + 'after' + ); + + unset( $preload_data[ $menus_data_path ] ); + return $preload_data; +} + +add_filter( 'block_editor_preload_data', 'gutenberg_navigation_editor_preload_menus', 10, 2 ); diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 92eff202ce743e..7ff03976a8b4b0 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -85,7 +85,7 @@ export const getEntityRecord = ( kind, name, key = '', query ) => async ( { // for how the request is made to the REST API. // eslint-disable-next-line @wordpress/no-unused-vars-before-return - const path = addQueryArgs( entity.baseURL + '/' + key, { + const path = addQueryArgs( entity.baseURL + ( key ? '/' + key : '' ), { ...entity.baseURLParams, ...query, } ); diff --git a/packages/edit-navigation/src/index.js b/packages/edit-navigation/src/index.js index 68a2328f714866..b31e46980bb2d6 100644 --- a/packages/edit-navigation/src/index.js +++ b/packages/edit-navigation/src/index.js @@ -92,3 +92,5 @@ export function initialize( id, settings ) { document.getElementById( id ) ); } + +export { createMenuPreloadingMiddleware as __unstableCreateMenuPreloadingMiddleware } from './utils'; diff --git a/packages/edit-navigation/src/utils/index.js b/packages/edit-navigation/src/utils/index.js new file mode 100644 index 00000000000000..2229f6a1c8b329 --- /dev/null +++ b/packages/edit-navigation/src/utils/index.js @@ -0,0 +1,122 @@ +/** + * The purpose of this function is to create a middleware that is responsible for preloading menu-related data. + * It uses data that is returned from the /__experimental/menus endpoint for requests + * to the /__experimental/menu/ endpoint, because the data is the same. + * This way, we can avoid making additional REST API requests. + * This middleware can be removed if/when we implement caching at the wordpress/core-data level. + * + * @param {Object} preloadedData + * @return {Function} Preloading middleware. + */ +export function createMenuPreloadingMiddleware( preloadedData ) { + const cache = Object.keys( preloadedData ).reduce( ( result, path ) => { + result[ getStablePath( path ) ] = preloadedData[ path ]; + return result; + }, /** @type {Record} */ ( {} ) ); + + let menusDataLoaded = false; + let menuDataLoaded = false; + + return ( options, next ) => { + const { parse = true } = options; + if ( 'string' !== typeof options.path ) { + return next( options ); + } + + const method = options.method || 'GET'; + if ( 'GET' !== method ) { + return next( options ); + } + + const path = getStablePath( options.path ); + if ( ! menusDataLoaded && cache[ path ] ) { + menusDataLoaded = true; + return sendSuccessResponse( cache[ path ], parse ); + } + + if ( menuDataLoaded ) { + return next( options ); + } + + const matches = path.match( + /^\/__experimental\/menus\/(\d+)\?context=edit$/ + ); + if ( ! matches ) { + return next( options ); + } + + const key = Object.keys( cache )?.[ 0 ]; + const menuData = cache[ key ]?.body; + if ( ! menuData ) { + return next( options ); + } + + const menuId = parseInt( matches[ 1 ] ); + const menu = menuData.filter( ( { id } ) => id === menuId ); + + if ( menu.length > 0 ) { + menuDataLoaded = true; + // We don't have headers because we "emulate" this request + return sendSuccessResponse( + { body: menu[ 0 ], headers: {} }, + parse + ); + } + + return next( options ); + }; +} + +/** + * This is a helper function that sends a success response. + * + * @param {Object} responseData An object with the menu data + * @param {boolean} parse A boolean that controls whether to send a response or just the response data + * @return {Object} Resolved promise + */ +function sendSuccessResponse( responseData, parse ) { + return Promise.resolve( + parse + ? responseData.body + : new window.Response( JSON.stringify( responseData.body ), { + status: 200, + statusText: 'OK', + headers: responseData.headers, + } ) + ); +} + +/** + * Given a path, returns a normalized path where equal query parameter values + * will be treated as identical, regardless of order they appear in the original + * text. + * + * @param {string} path Original path. + * + * @return {string} Normalized path. + */ +export function getStablePath( path ) { + const splitted = path.split( '?' ); + const query = splitted[ 1 ]; + const base = splitted[ 0 ]; + if ( ! query ) { + return base; + } + + // 'b=1&c=2&a=5' + return ( + base + + '?' + + query + // [ 'b=1', 'c=2', 'a=5' ] + .split( '&' ) + // [ [ 'b, '1' ], [ 'c', '2' ], [ 'a', '5' ] ] + .map( ( entry ) => entry.split( '=' ) ) + // [ [ 'a', '5' ], [ 'b, '1' ], [ 'c', '2' ] ] + .sort( ( a, b ) => a[ 0 ].localeCompare( b[ 0 ] ) ) + // [ 'a=5', 'b=1', 'c=2' ] + .map( ( pair ) => pair.join( '=' ) ) + // 'a=5&b=1&c=2' + .join( '&' ) + ); +} diff --git a/phpunit/navigation-page-test.php b/phpunit/navigation-page-test.php new file mode 100644 index 00000000000000..eb1558cbd4518c --- /dev/null +++ b/phpunit/navigation-page-test.php @@ -0,0 +1,85 @@ +callback = $this->createMock( WP_Navigation_Page_Test_Callback::class ); + add_filter( 'navigation_editor_preload_paths', array( $this->callback, 'preload_paths_callback' ) ); + add_filter( 'wp_get_nav_menus', array( $this->callback, 'wp_nav_menus_callback' ) ); + } + + public function tearDown() { + parent::tearDown(); + remove_filter( 'navigation_editor_preload_paths', array( $this->callback, 'preload_paths_callback' ) ); + remove_filter( 'wp_get_nav_menus', array( $this->callback, 'wp_nav_menus_callback' ) ); + } + + public function test_gutenberg_navigation_init_function_generates_correct_preload_paths() { + $menu_id = mt_rand( 1, 1000 ); + $expected_preload_paths = array( + '/__experimental/menu-locations', + array( + '/wp/v2/pages', + 'OPTIONS', + ), + array( + '/wp/v2/posts', + 'OPTIONS', + ), + '/__experimental/menus?per_page=100&context=edit&_locale=user', + '/wp/v2/types?context=edit', + "/__experimental/menu-items?context=edit&menus={$menu_id}&per_page=100&_locale=user", + ); + + $this->callback->expects( $this->once() ) + ->method( 'preload_paths_callback' ) + ->with( $expected_preload_paths ) + ->willReturn( array() ); + + $menu = new stdClass(); + $menu->term_id = $menu_id; + $this->callback->expects( $this->once() ) + ->method( 'wp_nav_menus_callback' ) + ->with( array() ) + ->willReturn( array( new WP_Term( $menu ) ) ); + + set_current_screen( 'gutenberg_page_gutenberg-navigation' ); + gutenberg_navigation_init( 'gutenberg_page_gutenberg-navigation' ); + } + + public function test_gutenberg_navigation_editor_preload_menus_function_returns_correct_data() { + $menus_endpoint = gutenberg_navigation_get_menus_endpoint(); + $preload_data = array( + '/__experimental/menu-locations' => array( 'some menu locations' ), + 'OPTIONS' => array( + array( 'some options requests' ), + ), + $menus_endpoint => ( 'some menus' ), + ); + + $result = gutenberg_navigation_editor_preload_menus( $preload_data, 'navigation_editor' ); + $this->assertArrayHasKey( '/__experimental/menu-locations', $result ); + $this->assertArrayHasKey( 'OPTIONS', $result ); + $this->assertArrayNotHasKey( $menus_endpoint, $result ); + } +} + + +/** + * This is a utility test class for creating mocks of the callback functions + */ +class WP_Navigation_Page_Test_Callback { + + public function preload_paths_callback() {} + public function wp_nav_menus_callback() {} +} From 4bbd27b5e1dffedc0c49c5ca962aec788db6d415 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 19 Oct 2021 14:45:10 +0000 Subject: [PATCH 13/28] Heading: Remove anchor map when block unmounts (#35761) --- packages/block-library/src/heading/edit.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index 9ca81e0410a66b..bd1b4b3c36c8ea 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -50,6 +50,10 @@ function HeadingEdit( { } ); } allHeadingAnchors[ clientId ] = anchor; + + return () => { + delete allHeadingAnchors[ clientId ]; + }; }, [ content, anchor ] ); const onContentChange = ( value ) => { From 9799046d318efc622550956019554c3f585e5d34 Mon Sep 17 00:00:00 2001 From: Anton Vlasenko <43744263+anton-vlasenko@users.noreply.github.com> Date: Tue, 19 Oct 2021 17:28:21 +0200 Subject: [PATCH 14/28] Fix preloading middleware referencing stale data for OPTIONS requests (#35527) * OPTIONS type requests should be cached the same way as GET type requets. * Fix wording in the comments. * Implement a unit test. --- .../api-fetch/src/middlewares/preloading.js | 13 +++++---- .../src/middlewares/test/preloading.js | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/api-fetch/src/middlewares/preloading.js b/packages/api-fetch/src/middlewares/preloading.js index a4658baefc8427..6fba40edde4951 100644 --- a/packages/api-fetch/src/middlewares/preloading.js +++ b/packages/api-fetch/src/middlewares/preloading.js @@ -52,7 +52,7 @@ function createPreloadingMiddleware( preloadedData ) { if ( 'GET' === method && cache[ path ] ) { const cacheData = cache[ path ]; - // Unsetting the cache key ensures that the data is only preloaded a single time + // Unsetting the cache key ensures that the data is only used a single time delete cache[ path ]; return Promise.resolve( @@ -72,11 +72,12 @@ function createPreloadingMiddleware( preloadedData ) { cache[ method ] && cache[ method ][ path ] ) { - return Promise.resolve( - parse - ? cache[ method ][ path ].body - : cache[ method ][ path ] - ); + const cacheData = cache[ method ][ path ]; + + // Unsetting the cache key ensures that the data is only used a single time + delete cache[ method ][ path ]; + + return Promise.resolve( parse ? cacheData.body : cacheData ); } } diff --git a/packages/api-fetch/src/middlewares/test/preloading.js b/packages/api-fetch/src/middlewares/test/preloading.js index 3f470e9a8a6e69..4add5a46424cc9 100644 --- a/packages/api-fetch/src/middlewares/test/preloading.js +++ b/packages/api-fetch/src/middlewares/test/preloading.js @@ -199,6 +199,34 @@ describe( 'Preloading Middleware', () => { expect( value ).toEqual( body ); } ); + it( 'should remove OPTIONS type requests from the cache after the first hit', async () => { + const body = { content: 'example' }; + const preloadedData = { + OPTIONS: { + 'wp/v2/demo': { body }, + }, + }; + + const preloadingMiddleware = createPreloadingMiddleware( + preloadedData + ); + + const requestOptions = { + method: 'OPTIONS', + path: 'wp/v2/demo', + }; + + const firstMiddleware = jest.fn(); + preloadingMiddleware( requestOptions, firstMiddleware ); + expect( firstMiddleware ).not.toHaveBeenCalled(); + + await preloadingMiddleware( requestOptions, firstMiddleware ); + + const secondMiddleware = jest.fn(); + await preloadingMiddleware( requestOptions, secondMiddleware ); + expect( secondMiddleware ).toHaveBeenCalledTimes( 1 ); + } ); + describe.each( [ [ 'GET' ], [ 'OPTIONS' ] ] )( '%s', ( method ) => { describe.each( [ [ 'all empty', {} ], From fac0447126ce36e6a5ccd7591772ba5074b4d652 Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+c4rl0sbr4v0@users.noreply.github.com> Date: Tue, 19 Oct 2021 17:37:58 +0200 Subject: [PATCH 15/28] [Block: Post comment author]: Add link settings and block supports (#35595) * Update post comment author icon * Update post comment author with new options * Refactor to move the link wrapper outside the select function * Fix linter comma trailing on php * Fixed some comments * Add p tag inside, renamed icon * Unify removing p tag on both editor and frontend * Reset p to previous status Co-authored-by: Carlos Bravo --- .../src/post-comment-author/block.json | 25 ++++++++- .../src/post-comment-author/edit.js | 56 ++++++++++++++++--- .../src/post-comment-author/index.js | 2 +- .../src/post-comment-author/index.php | 10 +++- packages/icons/src/index.js | 1 + packages/icons/src/library/comment-author.js | 22 ++++++++ .../blocks/core__post-comment-author.json | 5 +- 7 files changed, 109 insertions(+), 12 deletions(-) create mode 100644 packages/icons/src/library/comment-author.js diff --git a/packages/block-library/src/post-comment-author/block.json b/packages/block-library/src/post-comment-author/block.json index c3a2231cc5d10b..7faa0b7ad9f2aa 100644 --- a/packages/block-library/src/post-comment-author/block.json +++ b/packages/block-library/src/post-comment-author/block.json @@ -6,8 +6,31 @@ "parent": [ "core/post-comment" ], "description": "Post comment author.", "textdomain": "default", + "attributes": { + "isLink": { + "type": "boolean", + "default": false + }, + "linkTarget": { + "type": "string", + "default": "_self" + } + }, "usesContext": [ "commentId" ], "supports": { - "html": false + "html": false, + "color": { + "gradients": true, + "link": true + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalFontWeight": true, + "__experimentalFontStyle": true, + "__experimentalTextTransform": true, + "__experimentalLetterSpacing": true + } } } diff --git a/packages/block-library/src/post-comment-author/edit.js b/packages/block-library/src/post-comment-author/edit.js index d63d1cf5a958fa..ed4a3f4733ae5f 100644 --- a/packages/block-library/src/post-comment-author/edit.js +++ b/packages/block-library/src/post-comment-author/edit.js @@ -3,12 +3,24 @@ */ import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; -import { useBlockProps } from '@wordpress/block-editor'; +import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; import { store as coreStore } from '@wordpress/core-data'; +import { PanelBody, ToggleControl } from '@wordpress/components'; -export default function Edit( { attributes, context } ) { - const { className } = attributes; +/** + * Renders the `core/post-comment-author` block on the editor. + * + * @param {Object} props React props. + * @param {Object} props.setAttributes Callback for updating block attributes. + * @param {Object} props.attributes Block attributes. + * @param {Object} props.context Inherited context. + * + * @return {JSX.Element} React element. + */ +export default function Edit( { attributes, context, setAttributes } ) { + const { className, isLink, linkTarget } = attributes; const { commentId } = context; + const blockProps = useBlockProps( { className } ); const displayName = useSelect( ( select ) => { @@ -21,15 +33,45 @@ export default function Edit( { attributes, context } ) { const user = getEntityRecord( 'root', 'user', comment.author ); return user?.name ?? __( 'Anonymous' ); } - return authorName ?? ''; }, [ commentId ] ); + const displayAuthor = isLink ? ( + event.preventDefault() } + > + { displayName } + + ) : ( +

{ displayName }

+ ); + return ( -
-

{ displayName }

-
+ <> + + + setAttributes( { isLink: ! isLink } ) } + checked={ isLink } + /> + { isLink && ( + + setAttributes( { + linkTarget: value ? '_blank' : '_self', + } ) + } + checked={ linkTarget === '_blank' } + /> + ) } + + +
{ displayAuthor }
+ ); } diff --git a/packages/block-library/src/post-comment-author/index.js b/packages/block-library/src/post-comment-author/index.js index 68fb2c2f737c25..e6779fa3ec22a8 100644 --- a/packages/block-library/src/post-comment-author/index.js +++ b/packages/block-library/src/post-comment-author/index.js @@ -7,7 +7,7 @@ import edit from './edit'; /** * WordPress dependencies */ -import { postAuthor as icon } from '@wordpress/icons'; +import { commentAuthor as icon } from '@wordpress/icons'; const { name } = metadata; export { metadata, name }; diff --git a/packages/block-library/src/post-comment-author/index.php b/packages/block-library/src/post-comment-author/index.php index 9cad587d3d447c..430b0c8d4b254f 100644 --- a/packages/block-library/src/post-comment-author/index.php +++ b/packages/block-library/src/post-comment-author/index.php @@ -11,7 +11,7 @@ * @param array $attributes Block attributes. * @param string $content Block default content. * @param WP_Block $block Block instance. - * @return string Return the post comment's content. + * @return string Return the post comment's author. */ function render_block_core_post_comment_author( $attributes, $content, $block ) { if ( ! isset( $block->context['commentId'] ) ) { @@ -19,11 +19,17 @@ function render_block_core_post_comment_author( $attributes, $content, $block ) } $wrapper_attributes = get_block_wrapper_attributes(); + $comment_author = get_comment_author( $block->context['commentId'] ); + $link = get_comment_author_url( $block->context['commentId'] ); + + if ( ! empty( $attributes['isLink'] ) && ! empty( $attributes['linkTarget'] ) ) { + $comment_author = sprintf( '%3s', $link, $attributes['linkTarget'], $comment_author ); + } return sprintf( '
%2$s
', $wrapper_attributes, - get_comment_author( $block->context['commentId'] ) + $comment_author ); } diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js index 15ece72ab4c7e6..360f93e673cf11 100644 --- a/packages/icons/src/index.js +++ b/packages/icons/src/index.js @@ -49,6 +49,7 @@ export { default as color } from './library/color'; export { default as column } from './library/column'; export { default as columns } from './library/columns'; export { default as comment } from './library/comment'; +export { default as commentAuthor } from './library/comment-author'; export { default as cover } from './library/cover'; export { default as create } from './library/create'; export { default as crop } from './library/crop'; diff --git a/packages/icons/src/library/comment-author.js b/packages/icons/src/library/comment-author.js new file mode 100644 index 00000000000000..1d55d90a491a15 --- /dev/null +++ b/packages/icons/src/library/comment-author.js @@ -0,0 +1,22 @@ +/** + * WordPress dependencies + */ +import { SVG, Path, Circle } from '@wordpress/primitives'; + +const commentAuthor = ( + + + + + +); + +export default commentAuthor; diff --git a/test/integration/fixtures/blocks/core__post-comment-author.json b/test/integration/fixtures/blocks/core__post-comment-author.json index 4e476b90209128..1418da92d5db65 100644 --- a/test/integration/fixtures/blocks/core__post-comment-author.json +++ b/test/integration/fixtures/blocks/core__post-comment-author.json @@ -3,7 +3,10 @@ "clientId": "_clientId_0", "name": "core/post-comment-author", "isValid": true, - "attributes": {}, + "attributes": { + "isLink": false, + "linkTarget": "_self" + }, "innerBlocks": [], "originalContent": "" } From 96a900cff0391a0eac39e2bd43cdabcfe4b7b69d Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Tue, 19 Oct 2021 18:20:16 +0200 Subject: [PATCH 16/28] Mobile - iOS Runner - Fix macos version from latest to 11 (#35770) --- .github/workflows/rnmobile-ios-runner.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index 023465359c0fff..fa587db0a7b95f 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -14,7 +14,7 @@ concurrency: jobs: test: - runs-on: macos-latest + runs-on: macos-11 if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} strategy: matrix: From d8682025f040a575c157e46669fed4fbb33b6498 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Tue, 19 Oct 2021 09:34:16 -0700 Subject: [PATCH 17/28] Add section on using theme.json schema (#35739) A section and example on using the theme.json schema from SchemaStore for developing with the theme.json in your editor. --- docs/how-to-guides/themes/theme-json.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md index c4634acbbbfdc2..0eb28f9a29e0e4 100644 --- a/docs/how-to-guides/themes/theme-json.md +++ b/docs/how-to-guides/themes/theme-json.md @@ -965,6 +965,15 @@ Currently block variations exist for "header" and "footer" values of the area te } ``` +## Developing with theme.json + +It can be difficult to remember the theme.json settings and properties while you develop, so a JSON scheme was created to help. The schema is available at [SchemaStore.org](https://schemastore.org/) + +To use the schema, add `"$schema": "https://json.schemastore.org/theme-v1.json"` to the beginning of your theme.json file. Visual Studio Code and other editors will pick up the schema and can provide help like tooltips, autocomplete, or schema validation in the editor. + +![Example using validation with schema](https://developer.wordpress.org/files/2021/10/schema-validation.gif) + + ## Frequently Asked Questions ### The naming schema of CSS Custom Properties From e192d5058b90cd906e4955a24640f0c94a541a77 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 19 Oct 2021 18:24:19 +0000 Subject: [PATCH 18/28] Heading: Fix undo/redo "trap" (#35767) --- packages/block-library/src/heading/edit.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index bd1b4b3c36c8ea..4694ccde5a46e5 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -8,12 +8,14 @@ import classnames from 'classnames'; */ import { __ } from '@wordpress/i18n'; import { useEffect } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; import { AlignmentControl, BlockControls, RichText, useBlockProps, + store as blockEditorStore, } from '@wordpress/block-editor'; /** @@ -41,16 +43,22 @@ function HeadingEdit( { style, } ); + const { __unstableMarkNextChangeAsNotPersistent } = useDispatch( + blockEditorStore + ); + // Initially set anchor for headings that have content but no anchor set. // This is used when transforming a block to heading, or for legacy anchors. useEffect( () => { if ( ! anchor && content ) { + // This side-effect should not create an undo level. + __unstableMarkNextChangeAsNotPersistent(); setAttributes( { anchor: generateAnchor( clientId, content, allHeadingAnchors ), } ); } - allHeadingAnchors[ clientId ] = anchor; + allHeadingAnchors[ clientId ] = anchor; return () => { delete allHeadingAnchors[ clientId ]; }; From a32aae5f134d833c6917fb533299cbec20d2f783 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 20 Oct 2021 16:03:42 +1100 Subject: [PATCH 19/28] Border Radius Control: Add fallback px unit and add utils tests (#35786) * Border Radius Control: Add fallback px unit and add utils tests * Tweak comments * Fix typo (change "passing" to "parsing") Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> --- .../components/border-radius-control/index.js | 3 +- .../border-radius-control/test/utils.js | 216 ++++++++++++++++++ .../components/border-radius-control/utils.js | 28 ++- 3 files changed, 236 insertions(+), 11 deletions(-) create mode 100644 packages/block-editor/src/components/border-radius-control/test/utils.js diff --git a/packages/block-editor/src/components/border-radius-control/index.js b/packages/block-editor/src/components/border-radius-control/index.js index ed154caf4e3bfc..ae501b60d68400 100644 --- a/packages/block-editor/src/components/border-radius-control/index.js +++ b/packages/block-editor/src/components/border-radius-control/index.js @@ -53,7 +53,8 @@ export default function BorderRadiusControl( { onChange, values } ) { const units = useCustomUnits( { availableUnits: useSetting( 'spacing.units' ) || [ 'px', 'em', 'rem' ], } ); - const unit = getAllUnit( values ) || 'px'; + + const unit = getAllUnit( values ); const unitConfig = units && units.find( ( item ) => item.value === unit ); const step = unitConfig?.step || 1; diff --git a/packages/block-editor/src/components/border-radius-control/test/utils.js b/packages/block-editor/src/components/border-radius-control/test/utils.js new file mode 100644 index 00000000000000..50d932322a02f3 --- /dev/null +++ b/packages/block-editor/src/components/border-radius-control/test/utils.js @@ -0,0 +1,216 @@ +/** + * Internal dependencies + */ +import { + getAllUnit, + getAllValue, + hasMixedValues, + hasDefinedValues, + mode, +} from '../utils'; + +describe( 'getAllUnit', () => { + describe( 'when provided string based values', () => { + it( 'should return valid unit when passed a valid unit', () => { + expect( getAllUnit( '32em' ) ).toBe( 'em' ); + } ); + + it( 'should fall back to px when passed an invalid unit', () => { + expect( getAllUnit( '32apples' ) ).toBe( 'px' ); + } ); + + it( 'should fall back to px when passed a value without a unit', () => { + expect( getAllUnit( '32' ) ).toBe( 'px' ); + } ); + } ); + + describe( 'when provided object based values', () => { + it( 'should return the most common value', () => { + const values = { + bottomLeft: '2em', + bottomRight: '2em', + topLeft: '0', + topRight: '2px', + }; + expect( getAllUnit( values ) ).toBe( 'em' ); + } ); + + it( 'should return the real value when the most common value is undefined', () => { + const values = { + bottomLeft: '0', + bottomRight: '0', + topLeft: '0', + topRight: '2em', + }; + expect( getAllUnit( values ) ).toBe( 'em' ); + } ); + + it( 'should return the most common value there are no undefined values', () => { + const values = { + bottomLeft: '1em', + bottomRight: '1em', + topLeft: '2px', + topRight: '2em', + }; + expect( getAllUnit( values ) ).toBe( 'em' ); + } ); + + it( 'should fall back to px when all values are undefined or equivalent', () => { + const values = { + bottomLeft: '0', + bottomRight: undefined, + topLeft: undefined, + topRight: '0', + }; + expect( getAllUnit( values ) ).toBe( 'px' ); + } ); + } ); + + describe( 'when provided invalid values', () => { + it( 'should return px when passed an array', () => { + expect( getAllUnit( [] ) ).toBe( 'px' ); + } ); + it( 'should return px when passed a boolean', () => { + expect( getAllUnit( false ) ).toBe( 'px' ); + } ); + it( 'should return px when passed undefined', () => { + expect( getAllUnit( false ) ).toBe( 'px' ); + } ); + } ); +} ); + +describe( 'getAllValue', () => { + describe( 'when provided string based values', () => { + it( 'should return valid value + unit when passed a valid unit', () => { + expect( getAllValue( '32em' ) ).toBe( '32em' ); + } ); + + it( 'should return string as-is without parsing it', () => { + expect( getAllValue( '32apples' ) ).toBe( '32apples' ); + } ); + } ); + + describe( 'when provided object based values', () => { + it( 'should return null if values are mixed', () => { + const values = { + bottomLeft: '2em', + bottomRight: '2em', + topLeft: '0', + topRight: '2px', + }; + expect( getAllValue( values ) ).toBe( null ); + } ); + + it( 'should return the common value + unit when all values are the same', () => { + const values = { + bottomLeft: '1em', + bottomRight: '1em', + topLeft: '1em', + topRight: '1em', + }; + expect( getAllValue( values ) ).toBe( '1em' ); + } ); + + it( 'should return the common value + most common unit when same values but different units', () => { + const values = { + bottomLeft: '1em', + bottomRight: '1em', + topLeft: '1px', + topRight: '1rem', + }; + expect( getAllValue( values ) ).toBe( '1em' ); + } ); + + it( 'should fall back to null when values are undefined', () => { + const values = { + bottomLeft: undefined, + bottomRight: undefined, + topLeft: undefined, + topRight: undefined, + }; + expect( getAllValue( values ) ).toBe( null ); + } ); + } ); + + describe( 'when provided invalid values', () => { + it( 'should return px when passed an array', () => { + expect( getAllValue( [] ) ).toBe( null ); + } ); + it( 'should return px when passed a boolean', () => { + expect( getAllValue( false ) ).toBe( null ); + } ); + it( 'should return px when passed undefined', () => { + expect( getAllValue( false ) ).toBe( null ); + } ); + } ); +} ); + +describe( 'hasMixedValues', () => { + it( 'should return false when passed a string value', () => { + expect( hasMixedValues( '2px' ) ).toBe( false ); + } ); + + it( 'should return true when passed mixed values', () => { + const values = { + bottomLeft: '1em', + bottomRight: '1px', + topLeft: '2px', + topRight: '2em', + }; + expect( hasMixedValues( values ) ).toBe( true ); + } ); + + it( 'should return false when passed a common value', () => { + const values = { + bottomLeft: '1em', + bottomRight: '1em', + topLeft: '1em', + topRight: '1em', + }; + expect( hasMixedValues( values ) ).toBe( false ); + } ); +} ); + +describe( 'hasDefinedValues', () => { + it( 'should return false when passed a falsy value', () => { + expect( hasDefinedValues( undefined ) ).toBe( false ); + expect( hasDefinedValues( null ) ).toBe( false ); + expect( hasDefinedValues( '' ) ).toBe( false ); + } ); + + it( 'should return true when passed a non empty string value', () => { + expect( hasDefinedValues( '1px' ) ).toBe( true ); + } ); + + it( 'should return false when passed an object with empty values', () => { + const values = { + bottomLeft: undefined, + bottomRight: undefined, + topLeft: undefined, + topRight: undefined, + }; + expect( hasDefinedValues( values ) ).toBe( false ); + } ); + + it( 'should return true when passed an object with at least one real value', () => { + const values = { + bottomLeft: undefined, + bottomRight: '1px', + topLeft: undefined, + topRight: undefined, + }; + expect( hasDefinedValues( values ) ).toBe( true ); + } ); +} ); + +describe( 'mode', () => { + it( 'should return the most common value', () => { + const values = [ 'a', 'z', 'z', 'b', undefined ]; + expect( mode( values ) ).toBe( 'z' ); + } ); + + it( 'should return the most common real value', () => { + const values = [ undefined, 'a', undefined, undefined, undefined ]; + expect( mode( values ) ).toBe( 'a' ); + } ); +} ); diff --git a/packages/block-editor/src/components/border-radius-control/utils.js b/packages/block-editor/src/components/border-radius-control/utils.js index a5bf6e176c92db..0f628d6dce068b 100644 --- a/packages/block-editor/src/components/border-radius-control/utils.js +++ b/packages/block-editor/src/components/border-radius-control/utils.js @@ -4,27 +4,35 @@ import { __experimentalParseUnit as parseUnit } from '@wordpress/components'; /** - * Gets the item with the highest occurrence within an array - * https://stackoverflow.com/a/20762713 + * Gets the (non-undefined) item with the highest occurrence within an array + * Based in part on: https://stackoverflow.com/a/20762713 * - * @param {Array} arr Array of items to check. - * @return {any} The item with the most occurrences. + * Undefined values are always sorted to the end by `sort`, so this function + * returns the first element, to always prioritize real values over undefined + * values. + * + * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#description + * + * @param {Array} inputArray Array of items to check. + * @return {any} The item with the most occurrences. */ -function mode( arr ) { +export function mode( inputArray ) { + const arr = [ ...inputArray ]; return arr .sort( ( a, b ) => - arr.filter( ( v ) => v === a ).length - - arr.filter( ( v ) => v === b ).length + inputArray.filter( ( v ) => v === b ).length - + inputArray.filter( ( v ) => v === a ).length ) - .pop(); + .shift(); } /** * Returns the most common CSS unit in the radius values. + * Falls back to `px` as a default unit. * * @param {Object|string} values Radius values. - * @return {string} Most common CSS unit in values. + * @return {string} Most common CSS unit in values. Default: `px`. */ export function getAllUnit( values = {} ) { if ( typeof values === 'string' ) { @@ -37,7 +45,7 @@ export function getAllUnit( values = {} ) { return unit; } ); - return mode( allUnits ); + return mode( allUnits ) || 'px'; } /** From f7b26abf34542fc0a801a2b48564e38c9685bda6 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad Date: Wed, 20 Oct 2021 01:50:51 -0400 Subject: [PATCH 20/28] Add spacing controls to the featured image block (#35775) * Add spacing controls to the featured image block. --- packages/block-library/src/post-featured-image/block.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/post-featured-image/block.json b/packages/block-library/src/post-featured-image/block.json index 4ca50131925d8f..49116ef908199a 100644 --- a/packages/block-library/src/post-featured-image/block.json +++ b/packages/block-library/src/post-featured-image/block.json @@ -29,7 +29,11 @@ "text": false, "background": false }, - "html": false + "html": false, + "spacing": { + "margin": true, + "padding": true + } }, "editorStyle": "wp-block-post-featured-image-editor", "style": "wp-block-post-featured-image" From af01a389b88ff1819414efbcc3f7eea54b3d01e5 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Wed, 20 Oct 2021 10:46:32 +0200 Subject: [PATCH 21/28] Try: Fix navigation gap & padding issues. (#35752) --- packages/block-library/src/navigation/style.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index 9ee5f4548c3e6a..5cf54dadfe3df9 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -231,7 +231,7 @@ .wp-block-navigation, .wp-block-navigation .wp-block-page-list, .wp-block-navigation__container { - gap: calc(var(--wp--style--block-gap, 2em) / 4) var(--wp--style--block-gap, 2em); + gap: var(--wp--style--block-gap, 2em); } // Menu items with background. @@ -242,7 +242,7 @@ &, .wp-block-navigation .wp-block-page-list, .wp-block-navigation__container { - gap: calc(var(--wp--style--block-gap, 0) / 4) var(--wp--style--block-gap, 0.5em); + gap: var(--wp--style--block-gap, 0.5em); } } @@ -256,7 +256,7 @@ // When the menu has a background, items have paddings, reduce margins to compensate. // Treat margins and paddings differently when the block has a background. -.wp-block-navigation:where(.has-background) a { +.wp-block-navigation:where(.has-background) .wp-block-navigation-item__content { padding: 0.5em 1em; } @@ -465,7 +465,7 @@ } // A default padding is added to submenu items. It's not appropriate inside the modal. - & :where(.wp-block-navigation__submenu-container) a { + a { padding: 0; } From 9e07dbc6e68cbb16a60377e02a825dfef7d7d79e Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Wed, 20 Oct 2021 10:46:40 +0200 Subject: [PATCH 22/28] Enable inheritance in search block. (#35723) * Enable inheritance in search block. * Try lowering specificity. --- packages/block-library/src/search/style.scss | 106 ++++++++++--------- 1 file changed, 54 insertions(+), 52 deletions(-) diff --git a/packages/block-library/src/search/style.scss b/packages/block-library/src/search/style.scss index f857085aa972df..da8ca3e866aa83 100644 --- a/packages/block-library/src/search/style.scss +++ b/packages/block-library/src/search/style.scss @@ -1,69 +1,71 @@ -.wp-block-search { +.wp-block-search__button { + background: #f7f7f7; + border: 1px solid #ccc; + padding: 0.375em 0.625em; + color: #32373c; + margin-left: 0.625em; + word-break: normal; + font-size: inherit; + font-family: inherit; + line-height: inherit; - .wp-block-search__button { - background: #f7f7f7; - border: 1px solid #ccc; - padding: 0.375em 0.625em; - color: #32373c; - margin-left: 0.625em; - word-break: normal; - - &.has-icon { - line-height: 0; - } - - svg { - min-width: 1.5em; - min-height: 1.5em; - fill: currentColor; - } + &.has-icon { + line-height: 0; } - .wp-block-search__inside-wrapper { - display: flex; - flex: auto; - flex-wrap: nowrap; - max-width: 100%; + svg { + min-width: 1.5em; + min-height: 1.5em; + fill: currentColor; } +} - .wp-block-search__label { - width: 100%; - } +.wp-block-search__inside-wrapper { + display: flex; + flex: auto; + flex-wrap: nowrap; + max-width: 100%; +} - .wp-block-search__input { - padding: 8px; - flex-grow: 1; - min-width: 3em; - border: 1px solid #949494; - } +.wp-block-search__label { + width: 100%; +} - &.wp-block-search__button-only { - .wp-block-search__button { - margin-left: 0; - } - } +.wp-block-search__input { + padding: 8px; + flex-grow: 1; + min-width: 3em; + border: 1px solid #949494; + font-size: inherit; + font-family: inherit; + line-height: inherit; +} - &.wp-block-search__button-inside .wp-block-search__inside-wrapper { - padding: 4px; - border: 1px solid #949494; +.wp-block-search.wp-block-search__button-only { + .wp-block-search__button { + margin-left: 0; + } +} - .wp-block-search__input { - border-radius: 0; - border: none; - padding: 0 0 0 0.25em; +.wp-block-search.wp-block-search__button-inside .wp-block-search__inside-wrapper { + padding: 4px; + border: 1px solid #949494; - &:focus { - outline: none; - } - } + .wp-block-search__input { + border-radius: 0; + border: none; + padding: 0 0 0 0.25em; - .wp-block-search__button { - padding: 0.125em 0.5em; + &:focus { + outline: none; } } - &.aligncenter .wp-block-search__inside-wrapper { - margin: auto; + .wp-block-search__button { + padding: 0.125em 0.5em; } } +.wp-block-search.aligncenter .wp-block-search__inside-wrapper { + margin: auto; +} From 752d7759b373a1e748cabdc7f04cefcd99ccb684 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 20 Oct 2021 10:01:24 +0100 Subject: [PATCH 23/28] Fix insertion point animation (#35729) --- .../block-editor/src/components/block-tools/insertion-point.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/block-editor/src/components/block-tools/insertion-point.js b/packages/block-editor/src/components/block-tools/insertion-point.js index aec2e554473e90..1baae653794229 100644 --- a/packages/block-editor/src/components/block-tools/insertion-point.js +++ b/packages/block-editor/src/components/block-tools/insertion-point.js @@ -291,6 +291,9 @@ function InsertionPointPopover( { // Render in the old slot if needed for backward compatibility, // otherwise render in place (not in the the default popover slot). __unstableSlotName={ __unstablePopoverSlot || null } + // Forces a remount of the popover when its position changes + // This makes sure the popover doesn't animate from its previous position. + key={ nextClientId + '--' + rootClientId } > Date: Wed, 20 Oct 2021 19:24:12 +1000 Subject: [PATCH 24/28] Add block support panel specific className (#35793) --- .../components/inspector-controls/block-support-tools-panel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/inspector-controls/block-support-tools-panel.js b/packages/block-editor/src/components/inspector-controls/block-support-tools-panel.js index 170ecab88b624e..92479384b5bcb8 100644 --- a/packages/block-editor/src/components/inspector-controls/block-support-tools-panel.js +++ b/packages/block-editor/src/components/inspector-controls/block-support-tools-panel.js @@ -10,7 +10,7 @@ import { useDispatch, useSelect } from '@wordpress/data'; import { store as blockEditorStore } from '../../store'; import { cleanEmptyObject } from '../../hooks/utils'; -export default function BlockSupportToolsPanel( { children, label } ) { +export default function BlockSupportToolsPanel( { children, group, label } ) { const { clientId, attributes } = useSelect( ( select ) => { const { getBlockAttributes, getSelectedBlockClientId } = select( blockEditorStore @@ -46,6 +46,7 @@ export default function BlockSupportToolsPanel( { children, label } ) { return ( Date: Wed, 20 Oct 2021 11:54:24 +0200 Subject: [PATCH 25/28] Test that single parts can be updated but the untouched ones remain equal (#35759) --- phpunit/class-wp-theme-json-resolver-test.php | 18 ++++++++++++++++-- phpunit/data/themedir1/fse-child/theme.json | 4 ++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index b3986d1b9551a6..673be290090224 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -283,10 +283,24 @@ function test_merges_child_theme_json_into_parent_theme_json() { ), ), ), - 'custom' => true, + 'custom' => false, + 'link' => true, ), 'blocks' => array( - 'core/paragraph' => array( + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'light', + 'name' => 'Light', + 'color' => '#f5f7f9', + ), + ), + ), + ), + ), + 'core/post-title' => array( 'color' => array( 'palette' => array( 'theme' => array( diff --git a/phpunit/data/themedir1/fse-child/theme.json b/phpunit/data/themedir1/fse-child/theme.json index 4e3f75c503fe52..90fe35e758b455 100644 --- a/phpunit/data/themedir1/fse-child/theme.json +++ b/phpunit/data/themedir1/fse-child/theme.json @@ -19,10 +19,10 @@ "color": "#111827" } ], - "custom": true + "link": true }, "blocks": { - "core/paragraph": { + "core/post-title": { "color": { "palette": [ { From 3df8596c3433b0b38ec6348a1b9e227c860046a3 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 20 Oct 2021 12:24:32 +0000 Subject: [PATCH 26/28] FSE: Use slug as template parts area item key (#35796) --- .../src/components/sidebar/template-card/template-areas.js | 2 +- .../edit-site/src/components/template-details/template-areas.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/sidebar/template-card/template-areas.js b/packages/edit-site/src/components/sidebar/template-card/template-areas.js index b7af215bc0cb87..7762812fad558f 100644 --- a/packages/edit-site/src/components/sidebar/template-card/template-areas.js +++ b/packages/edit-site/src/components/sidebar/template-card/template-areas.js @@ -61,7 +61,7 @@ export default function TemplateAreas() {
    { templateParts.map( ( { templatePart, block } ) => ( -
  • +
  • { templateParts.map( ( { templatePart, block } ) => ( Date: Thu, 21 Oct 2021 01:36:20 +1300 Subject: [PATCH 27/28] Cover block: Change dimRatio to 50 if media added and dimRatio is set to 100 (#35789) * Change opacity to 50 if media added and opacity is set to 100 to prevent background color obscuring image * Fixes cover opacity bug on mobile (#35792) Co-authored-by: Glen Davies Co-authored-by: Antonis Lilis --- packages/block-library/src/cover/edit.js | 2 +- packages/block-library/src/cover/edit.native.js | 2 +- packages/block-library/src/cover/shared.js | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 7234e4215d6505..1d556e874282ca 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -333,7 +333,7 @@ function CoverEdit( { gradientValue, setGradient, } = __experimentalUseGradient(); - const onSelectMedia = attributesFromMedia( setAttributes ); + const onSelectMedia = attributesFromMedia( setAttributes, dimRatio ); const isUploadingMedia = isTemporaryMedia( id, url ); const [ prevMinHeightValue, setPrevMinHeightValue ] = useState( minHeight ); diff --git a/packages/block-library/src/cover/edit.native.js b/packages/block-library/src/cover/edit.native.js index b488f882bd73fc..56437afde080da 100644 --- a/packages/block-library/src/cover/edit.native.js +++ b/packages/block-library/src/cover/edit.native.js @@ -190,7 +190,7 @@ const Cover = ( { const onSelectMedia = ( media ) => { setDidUploadFail( false ); - const onSelect = attributesFromMedia( setAttributes ); + const onSelect = attributesFromMedia( setAttributes, dimRatio ); onSelect( media ); }; diff --git a/packages/block-library/src/cover/shared.js b/packages/block-library/src/cover/shared.js index 5c4c20a7cb4ec6..4a45c145518f0e 100644 --- a/packages/block-library/src/cover/shared.js +++ b/packages/block-library/src/cover/shared.js @@ -32,7 +32,7 @@ export function dimRatioToClass( ratio ) { : 'has-background-dim-' + 10 * Math.round( ratio / 10 ); } -export function attributesFromMedia( setAttributes ) { +export function attributesFromMedia( setAttributes, dimRatio ) { return ( media ) => { if ( ! media || ! media.url ) { setAttributes( { url: undefined, id: undefined } ); @@ -65,6 +65,7 @@ export function attributesFromMedia( setAttributes ) { } setAttributes( { + dimRatio: dimRatio === 100 ? 50 : dimRatio, url: media.url, id: media.id, alt: media?.alt, From 1a3bda10d3d04233e864d89eb9db3ca2adb0e62c Mon Sep 17 00:00:00 2001 From: Kjell Reigstad Date: Wed, 20 Oct 2021 09:52:26 -0400 Subject: [PATCH 28/28] Add spacing controls to all heading blocks (#35772) * Add spacing controls to all heading blocks. * Try just margin. --- packages/block-library/src/heading/block.json | 3 +++ packages/block-library/src/post-title/block.json | 3 +++ packages/block-library/src/query-title/block.json | 3 +++ 3 files changed, 9 insertions(+) diff --git a/packages/block-library/src/heading/block.json b/packages/block-library/src/heading/block.json index 705c2f2e8725ff..c35c20e8f514a4 100644 --- a/packages/block-library/src/heading/block.json +++ b/packages/block-library/src/heading/block.json @@ -32,6 +32,9 @@ "color": { "link": true }, + "spacing": { + "margin": true + }, "typography": { "fontSize": true, "lineHeight": true, diff --git a/packages/block-library/src/post-title/block.json b/packages/block-library/src/post-title/block.json index 308eced2ce359f..c9bfc1ce38f300 100644 --- a/packages/block-library/src/post-title/block.json +++ b/packages/block-library/src/post-title/block.json @@ -35,6 +35,9 @@ "gradients": true, "link": true }, + "spacing": { + "margin": true + }, "typography": { "fontSize": true, "lineHeight": true, diff --git a/packages/block-library/src/query-title/block.json b/packages/block-library/src/query-title/block.json index 3e5d43fc50846d..352280417c9909 100644 --- a/packages/block-library/src/query-title/block.json +++ b/packages/block-library/src/query-title/block.json @@ -23,6 +23,9 @@ "color": { "gradients": true }, + "spacing": { + "margin": true + }, "typography": { "fontSize": true, "lineHeight": true,