diff --git a/.eslintrc.js b/.eslintrc.js index e997e7804beac..177f3cf35b8cc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -83,6 +83,72 @@ const restrictedImports = [ }, ]; +const restrictedSyntax = [ + // NOTE: We can't include the forward slash in our regex or + // we'll get a `SyntaxError` (Invalid regular expression: \ at end of pattern) + // here. That's why we use \\u002F in the regexes below. + { + selector: + 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]', + message: 'Path access on WordPress dependencies is not allowed.', + }, + { + selector: + 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' + + majorMinorRegExp + + '/]', + message: + 'Deprecated functions must be removed before releasing this version.', + }, + { + selector: + 'CallExpression[callee.object.name="page"][callee.property.name="waitFor"]', + message: + 'This method is deprecated. You should use the more explicit API methods available.', + }, + { + selector: + 'CallExpression[callee.object.name="page"][callee.property.name="waitForTimeout"]', + message: 'Prefer page.waitForSelector instead.', + }, + { + selector: 'JSXAttribute[name.name="id"][value.type="Literal"]', + message: + 'Do not use string literals for IDs; use withInstanceId instead.', + }, + { + // Discourage the usage of `Math.random()` as it's a code smell + // for UUID generation, for which we already have a higher-order + // component: `withInstanceId`. + selector: + 'CallExpression[callee.object.name="Math"][callee.property.name="random"]', + message: + 'Do not use Math.random() to generate unique IDs; use withInstanceId instead. (If you’re not generating unique IDs: ignore this message.)', + }, + { + selector: + 'CallExpression[callee.name="withDispatch"] > :function > BlockStatement > :not(VariableDeclaration,ReturnStatement)', + message: + 'withDispatch must return an object with consistent keys. Avoid performing logic in `mapDispatchToProps`.', + }, + { + selector: + 'LogicalExpression[operator="&&"][left.property.name="length"][right.type="JSXElement"]', + message: + 'Avoid truthy checks on length property rendering, as zero length is rendered verbatim.', + }, +]; + +/** `no-restricted-syntax` rules for components. */ +const restrictedSyntaxComponents = [ + { + selector: + 'JSXOpeningElement[name.name="Button"]:not(:has(JSXAttribute[name.name="__experimentalIsFocusable"])) JSXAttribute[name.name="disabled"]', + message: + '`disabled` used without the `__experimentalIsFocusable` prop. Disabling a control without maintaining focusability can cause accessibility issues, by hiding their presence from screen reader users, or preventing focus from returning to a trigger element. (Ignore this error if you truly mean to disable.)', + }, +]; + module.exports = { root: true, extends: [ @@ -147,63 +213,7 @@ module.exports = { disallowTypeAnnotations: false, }, ], - 'no-restricted-syntax': [ - 'error', - // NOTE: We can't include the forward slash in our regex or - // we'll get a `SyntaxError` (Invalid regular expression: \ at end of pattern) - // here. That's why we use \\u002F in the regexes below. - { - selector: - 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]', - message: - 'Path access on WordPress dependencies is not allowed.', - }, - { - selector: - 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' + - majorMinorRegExp + - '/]', - message: - 'Deprecated functions must be removed before releasing this version.', - }, - { - selector: - 'CallExpression[callee.object.name="page"][callee.property.name="waitFor"]', - message: - 'This method is deprecated. You should use the more explicit API methods available.', - }, - { - selector: - 'CallExpression[callee.object.name="page"][callee.property.name="waitForTimeout"]', - message: 'Prefer page.waitForSelector instead.', - }, - { - selector: 'JSXAttribute[name.name="id"][value.type="Literal"]', - message: - 'Do not use string literals for IDs; use withInstanceId instead.', - }, - { - // Discourage the usage of `Math.random()` as it's a code smell - // for UUID generation, for which we already have a higher-order - // component: `withInstanceId`. - selector: - 'CallExpression[callee.object.name="Math"][callee.property.name="random"]', - message: - 'Do not use Math.random() to generate unique IDs; use withInstanceId instead. (If you’re not generating unique IDs: ignore this message.)', - }, - { - selector: - 'CallExpression[callee.name="withDispatch"] > :function > BlockStatement > :not(VariableDeclaration,ReturnStatement)', - message: - 'withDispatch must return an object with consistent keys. Avoid performing logic in `mapDispatchToProps`.', - }, - { - selector: - 'LogicalExpression[operator="&&"][left.property.name="length"][right.type="JSXElement"]', - message: - 'Avoid truthy checks on length property rendering, as zero length is rendered verbatim.', - }, - ], + 'no-restricted-syntax': [ 'error', ...restrictedSyntax ], }, overrides: [ { @@ -262,12 +272,8 @@ module.exports = { rules: { 'no-restricted-syntax': [ 'error', - { - selector: - 'JSXOpeningElement[name.name="Button"]:not(:has(JSXAttribute[name.name="__experimentalIsFocusable"])) JSXAttribute[name.name="disabled"]', - message: - '`disabled` used without the `__experimentalIsFocusable` prop. Disabling a control without maintaining focusability can cause accessibility issues, by hiding their presence from screen reader users, or preventing focus from returning to a trigger element. (Ignore this error if you truly mean to disable.)', - }, + ...restrictedSyntax, + ...restrictedSyntaxComponents, ], }, }, @@ -390,6 +396,7 @@ module.exports = { rules: { 'no-restricted-syntax': [ 'error', + ...restrictedSyntax, { selector: ':matches(Literal[value=/--wp-admin-theme-/],TemplateElement[value.cooked=/--wp-admin-theme-/])', diff --git a/backport-changelog/6.6/6616.md b/backport-changelog/6.6/6616.md index 91261f78fb5c7..bb35d6c74493c 100644 --- a/backport-changelog/6.6/6616.md +++ b/backport-changelog/6.6/6616.md @@ -2,4 +2,6 @@ https://github.com/WordPress/wordpress-develop/pull/6616 * https://github.com/WordPress/gutenberg/pull/58409 * https://github.com/WordPress/gutenberg/pull/61328 -* https://github.com/WordPress/gutenberg/pull/61842 \ No newline at end of file +* https://github.com/WordPress/gutenberg/pull/61842 +* https://github.com/WordPress/gutenberg/pull/62199 +* https://github.com/WordPress/gutenberg/pull/62252 diff --git a/backport-changelog/6.6/6731.md b/backport-changelog/6.6/6731.md new file mode 100644 index 0000000000000..b867187325165 --- /dev/null +++ b/backport-changelog/6.6/6731.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6731 + +* https://github.com/WordPress/gutenberg/pull/62299 diff --git a/docs/how-to-guides/themes/global-settings-and-styles.md b/docs/how-to-guides/themes/global-settings-and-styles.md index a5c3e828a2d66..f71bd67bfaf2e 100644 --- a/docs/how-to-guides/themes/global-settings-and-styles.md +++ b/docs/how-to-guides/themes/global-settings-and-styles.md @@ -310,20 +310,21 @@ There's one special setting property, `appearanceTools`, which is a boolean and To retain backward compatibility, the existing `add_theme_support` declarations that configure the block editor are retrofit in the proper categories for the top-level section. For example, if a theme uses `add_theme_support('disable-custom-colors')`, it'll be the same as setting `settings.color.custom` to `false`. If the `theme.json` contains any settings, these will take precedence over the values declared via `add_theme_support`. This is the complete list of equivalences: -| add_theme_support | theme.json setting | -| --------------------------- | --------------------------------------------------------- | -| `custom-line-height` | Set `typography.lineHeight` to `true`. | -| `custom-spacing` | Set `spacing.padding` to `true`. | -| `custom-units` | Provide the list of units via `spacing.units`. | -| `disable-custom-colors` | Set `color.custom` to `false`. | -| `disable-custom-font-sizes` | Set `typography.customFontSize` to `false`. | -| `disable-custom-gradients` | Set `color.customGradient` to `false`. | -| `editor-color-palette` | Provide the list of colors via `color.palette`. | -| `editor-font-sizes` | Provide the list of font size via `typography.fontSizes`. | -| `editor-gradient-presets` | Provide the list of gradients via `color.gradients`. | -| `appearance-tools` | Set `appearanceTools` to `true`. | -| `border` | Set `border: color, radius, style, width` to `true`. | -| `link-color ` | Set `color.link` to `true`. | +| add_theme_support | theme.json setting | +| --------------------------- | ------------------------------------------------------------- | +| `custom-line-height` | Set `typography.lineHeight` to `true`. | +| `custom-spacing` | Set `spacing.padding` to `true`. | +| `custom-units` | Provide the list of units via `spacing.units`. | +| `disable-custom-colors` | Set `color.custom` to `false`. | +| `disable-custom-font-sizes` | Set `typography.customFontSize` to `false`. | +| `disable-custom-gradients` | Set `color.customGradient` to `false`. | +| `editor-color-palette` | Provide the list of colors via `color.palette`. | +| `editor-font-sizes` | Provide the list of font size via `typography.fontSizes`. | +| `editor-gradient-presets` | Provide the list of gradients via `color.gradients`. | +| `editor-spacing-sizes` | Provide the list of spacing sizes via `spacing.spacingSizes`. | +| `appearance-tools` | Set `appearanceTools` to `true`. | +| `border` | Set `border: color, radius, style, width` to `true`. | +| `link-color ` | Set `color.link` to `true`. | #### Presets diff --git a/docs/reference-guides/block-api/block-styles.md b/docs/reference-guides/block-api/block-styles.md index 90b6c06d18f59..b47b1a76a71f6 100644 --- a/docs/reference-guides/block-api/block-styles.md +++ b/docs/reference-guides/block-api/block-styles.md @@ -1,6 +1,6 @@ # Styles -Block Styles allow alternative styles to be applied to existing blocks. They work by adding a className to the block's wrapper. This className can be used to provide an alternative styling for the block if the block style is selected. See the [Getting Started with JavaScript tutorial](/docs/how-to-guides/javascript/) for a full example. +Block Styles allow alternative styles to be applied to existing blocks. They work by adding a className to the block's wrapper. This className can be used to provide an alternative styling for the block if the block style is selected. See the [Use styles and stylesheets](/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md) for a full example on how to apply styles to a block. _Example:_ diff --git a/docs/reference-guides/slotfills/README.md b/docs/reference-guides/slotfills/README.md index 043a50cb5186e..ea324f71b25e8 100644 --- a/docs/reference-guides/slotfills/README.md +++ b/docs/reference-guides/slotfills/README.md @@ -33,7 +33,7 @@ registerPlugin( 'post-status-info-test', { render: PluginPostStatusInfoTest } ); SlotFills are created using `createSlotFill`. This creates two components, `Slot` and `Fill` which are then used to create a new component that is exported on the `wp.plugins` global. -**Definition of the `PluginPostStatusInfo` SlotFill** ([see core code](https://github.com/WordPress/gutenberg/blob/HEAD/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js#L54)) +**Definition of the `PluginPostStatusInfo` SlotFill** ([see core code](https://github.com/WordPress/gutenberg/blob/HEAD/packages/editor/src/components/plugin-post-status-info/index.js#L55)) ```js /** @@ -61,34 +61,70 @@ export default PluginPostStatusInfo; This new Slot is then exposed in the editor. The example below is from core and represents the Summary panel. As we can see, the `` is wrapping all of the items that will appear in the panel. -Any items that have been added via the SlotFill ( see the example above ), will be included in the `fills` parameter and be displayed between the `` and `` components. +Any items that have been added via the SlotFill ( see the example above ), will be included in the `fills` parameter and be displayed in the end of the component. -See [core code](https://github.com/WordPress/gutenberg/tree/HEAD/packages/edit-post/src/components/sidebar/post-status/index.js#L26). +See [core code](https://github.com/WordPress/gutenberg/tree/HEAD/packages/editor/src/components/sidebar/post-summary.js#L39). ```js -const PostStatus = ( { isOpened, onTogglePanel } ) => ( - - - { ( fills ) => ( - <> - - - - - - - { fills } - - - ) } - - -); +export default function PostSummary( { onActionPerformed } ) { + const { isRemovedPostStatusPanel } = useSelect( ( select ) => { + // We use isEditorPanelRemoved to hide the panel if it was programatically removed. We do + // not use isEditorPanelEnabled since this panel should not be disabled through the UI. + const { isEditorPanelRemoved, getCurrentPostType } = + select( editorStore ); + return { + isRemovedPostStatusPanel: isEditorPanelRemoved( PANEL_NAME ), + postType: getCurrentPostType(), + }; + }, [] ); + + return ( + + + { ( fills ) => ( + <> + + + } + /> + + + + + + + { ! isRemovedPostStatusPanel && ( + + + + + + + + + + + + + + + + + + { fills } + + ) } + + + ) } + + + ); +} ``` ## Currently available SlotFills and examples diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index ad4e2fe105b0c..ad4a9f21872be 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -3559,53 +3559,32 @@ public static function get_from_editor_settings( $settings ) { // Deprecated theme supports. if ( isset( $settings['disableCustomColors'] ) ) { - if ( ! isset( $theme_settings['settings']['color'] ) ) { - $theme_settings['settings']['color'] = array(); - } $theme_settings['settings']['color']['custom'] = ! $settings['disableCustomColors']; } if ( isset( $settings['disableCustomGradients'] ) ) { - if ( ! isset( $theme_settings['settings']['color'] ) ) { - $theme_settings['settings']['color'] = array(); - } $theme_settings['settings']['color']['customGradient'] = ! $settings['disableCustomGradients']; } if ( isset( $settings['disableCustomFontSizes'] ) ) { - if ( ! isset( $theme_settings['settings']['typography'] ) ) { - $theme_settings['settings']['typography'] = array(); - } $theme_settings['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; } if ( isset( $settings['enableCustomLineHeight'] ) ) { - if ( ! isset( $theme_settings['settings']['typography'] ) ) { - $theme_settings['settings']['typography'] = array(); - } $theme_settings['settings']['typography']['lineHeight'] = $settings['enableCustomLineHeight']; } if ( isset( $settings['enableCustomUnits'] ) ) { - if ( ! isset( $theme_settings['settings']['spacing'] ) ) { - $theme_settings['settings']['spacing'] = array(); - } $theme_settings['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? array( 'px', 'em', 'rem', 'vh', 'vw', '%' ) : $settings['enableCustomUnits']; } if ( isset( $settings['colors'] ) ) { - if ( ! isset( $theme_settings['settings']['color'] ) ) { - $theme_settings['settings']['color'] = array(); - } $theme_settings['settings']['color']['palette'] = $settings['colors']; } if ( isset( $settings['gradients'] ) ) { - if ( ! isset( $theme_settings['settings']['color'] ) ) { - $theme_settings['settings']['color'] = array(); - } $theme_settings['settings']['color']['gradients'] = $settings['gradients']; } @@ -3617,19 +3596,17 @@ public static function get_from_editor_settings( $settings ) { $font_sizes[ $key ]['size'] = $font_size['size'] . 'px'; } } - if ( ! isset( $theme_settings['settings']['typography'] ) ) { - $theme_settings['settings']['typography'] = array(); - } $theme_settings['settings']['typography']['fontSizes'] = $font_sizes; } if ( isset( $settings['enableCustomSpacing'] ) ) { - if ( ! isset( $theme_settings['settings']['spacing'] ) ) { - $theme_settings['settings']['spacing'] = array(); - } $theme_settings['settings']['spacing']['padding'] = $settings['enableCustomSpacing']; } + if ( isset( $settings['spacingSizes'] ) ) { + $theme_settings['settings']['spacing']['spacingSizes'] = $settings['spacingSizes']; + } + return $theme_settings; } @@ -3794,12 +3771,10 @@ public function get_data() { /** * Sets the spacingSizes array based on the spacingScale values from theme.json. * - * No longer used since theme.json version 3 as the spacingSizes are now - * automatically generated during construction and merge instead of manually - * set in the resolver. - * * @since 6.1.0 - * @deprecated 6.6.0 + * @deprecated 6.6.0 No longer used as the spacingSizes are automatically + * generated in the constructor and merge methods instead + * of manually after instantiation. * * @return null|void */ @@ -3852,6 +3827,10 @@ public function set_spacing_sizes() { * @return array The merged set of spacing sizes. */ private static function merge_spacing_sizes( $base, $incoming ) { + // Preserve the order if there are no base (spacingScale) values. + if ( empty( $base ) ) { + return $incoming; + } $merged = array(); foreach ( $base as $item ) { $merged[ $item['slug'] ] = $item; @@ -3859,6 +3838,7 @@ private static function merge_spacing_sizes( $base, $incoming ) { foreach ( $incoming as $item ) { $merged[ $item['slug'] ] = $item; } + ksort( $merged, SORT_NUMERIC ); return array_values( $merged ); } diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 84f999e4e9d02..a02ae74a91fda 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -220,6 +220,7 @@ protected static function has_same_registered_blocks( $origin ) { * @since 5.8.0 * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed. * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports. + * @since 6.6.0 Add support for 'default-font-sizes' and 'default-spacing-sizes' theme supports. * * @param array $deprecated Deprecated. Not used. * @param array $options { @@ -291,35 +292,27 @@ public static function get_theme_data( $deprecated = array(), $options = array() * So we take theme supports, transform it to theme.json shape * and merge the static::$theme upon that. */ - $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); + $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( gutenberg_get_classic_theme_supports_block_editor_settings() ); if ( ! wp_theme_has_theme_json() ) { - if ( ! isset( $theme_support_data['settings']['color'] ) ) { - $theme_support_data['settings']['color'] = array(); - } - - $default_palette = false; - if ( current_theme_supports( 'default-color-palette' ) ) { - $default_palette = true; - } - if ( ! isset( $theme_support_data['settings']['color']['palette'] ) ) { - // If the theme does not have any palette, we still want to show the core one. - $default_palette = true; - } - $theme_support_data['settings']['color']['defaultPalette'] = $default_palette; - - $default_gradients = false; - if ( current_theme_supports( 'default-gradient-presets' ) ) { - $default_gradients = true; - } - if ( ! isset( $theme_support_data['settings']['color']['gradients'] ) ) { - // If the theme does not have any gradients, we still want to show the core ones. - $default_gradients = true; - } - $theme_support_data['settings']['color']['defaultGradients'] = $default_gradients; + /* + * Unlike block themes, classic themes without a theme.json disable + * default presets when custom preset theme support is added. This + * behavior can be overridden by using the corresponding default + * preset theme support. + */ + $theme_support_data['settings']['color']['defaultPalette'] = + ! isset( $theme_support_data['settings']['color']['palette'] ) || + current_theme_supports( 'default-color-palette' ); + $theme_support_data['settings']['color']['defaultGradients'] = + ! isset( $theme_support_data['settings']['color']['gradients'] ) || + current_theme_supports( 'default-gradient-presets' ); + $theme_support_data['settings']['typography']['defaultFontSizes'] = + ! isset( $theme_support_data['settings']['typography']['fontSizes'] ) || + current_theme_supports( 'default-font-sizes' ); + $theme_support_data['settings']['spacing']['defaultSpacingSizes'] = + ! isset( $theme_support_data['settings']['spacing']['spacingSizes'] ) || + current_theme_supports( 'default-spacing-sizes' ); - if ( ! isset( $theme_support_data['settings']['shadow'] ) ) { - $theme_support_data['settings']['shadow'] = array(); - } /* * Shadow presets are explicitly disabled for classic themes until a * decision is made for whether the default presets should match the diff --git a/lib/class-wp-theme-json-schema-gutenberg.php b/lib/class-wp-theme-json-schema-gutenberg.php index 0def88f86a23a..43b6ea6dcc741 100644 --- a/lib/class-wp-theme-json-schema-gutenberg.php +++ b/lib/class-wp-theme-json-schema-gutenberg.php @@ -55,10 +55,9 @@ public static function migrate( $theme_json ) { switch ( $theme_json['version'] ) { case 1: $theme_json = self::migrate_v1_to_v2( $theme_json ); - // no break + // Deliberate fall through. Once migrated to v2, also migrate to v3. case 2: $theme_json = self::migrate_v2_to_v3( $theme_json ); - // no break } return $theme_json; @@ -97,7 +96,10 @@ private static function migrate_v1_to_v2( $old ) { /** * Migrates from v2 to v3. * - * - Sets settings.typography.defaultFontSizes to false. + * - Sets settings.typography.defaultFontSizes to false if settings.typography.fontSizes are defined. + * - Sets settings.spacing.defaultSpacingSizes to false if settings.spacing.spacingSizes are defined. + * - Prevents settings.spacing.spacingSizes from merging with settings.spacing.spacingScale by + * unsetting spacingScale when spacingSizes are defined. * * @since 6.6.0 * @@ -132,12 +134,6 @@ private static function migrate_v2_to_v3( $old ) { * when the theme did not provide any. */ if ( isset( $old['settings']['typography']['fontSizes'] ) ) { - if ( ! isset( $new['settings'] ) ) { - $new['settings'] = array(); - } - if ( ! isset( $new['settings']['typography'] ) ) { - $new['settings']['typography'] = array(); - } $new['settings']['typography']['defaultFontSizes'] = false; } @@ -151,12 +147,6 @@ private static function migrate_v2_to_v3( $old ) { isset( $old['settings']['spacing']['spacingSizes'] ) || isset( $old['settings']['spacing']['spacingScale'] ) ) { - if ( ! isset( $new['settings'] ) ) { - $new['settings'] = array(); - } - if ( ! isset( $new['settings']['spacing'] ) ) { - $new['settings']['spacing'] = array(); - } $new['settings']['spacing']['defaultSpacingSizes'] = false; } diff --git a/lib/compat/wordpress-6.6/block-editor.php b/lib/compat/wordpress-6.6/block-editor.php new file mode 100644 index 0000000000000..6253f6a0adca4 --- /dev/null +++ b/lib/compat/wordpress-6.6/block-editor.php @@ -0,0 +1,50 @@ + get_theme_support( 'disable-custom-colors' ), + 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), + 'disableCustomGradients' => get_theme_support( 'disable-custom-gradients' ), + 'disableLayoutStyles' => get_theme_support( 'disable-layout-styles' ), + 'enableCustomLineHeight' => get_theme_support( 'custom-line-height' ), + 'enableCustomSpacing' => get_theme_support( 'custom-spacing' ), + 'enableCustomUnits' => get_theme_support( 'custom-units' ), + ); + + // Theme settings. + $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) ); + if ( false !== $color_palette ) { + $theme_settings['colors'] = $color_palette; + } + + $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) ); + if ( false !== $font_sizes ) { + $theme_settings['fontSizes'] = $font_sizes; + } + + $gradient_presets = current( (array) get_theme_support( 'editor-gradient-presets' ) ); + if ( false !== $gradient_presets ) { + $theme_settings['gradients'] = $gradient_presets; + } + + $spacing_sizes = current( (array) get_theme_support( 'editor-spacing-sizes' ) ); + if ( false !== $spacing_sizes ) { + $theme_settings['spacingSizes'] = $spacing_sizes; + } + + return $theme_settings; +} diff --git a/lib/load.php b/lib/load.php index 1f63c816f8173..45b8f1a1ceb3b 100644 --- a/lib/load.php +++ b/lib/load.php @@ -133,6 +133,7 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.6 compat. require __DIR__ . '/compat/wordpress-6.6/admin-bar.php'; require __DIR__ . '/compat/wordpress-6.6/blocks.php'; +require __DIR__ . '/compat/wordpress-6.6/block-editor.php'; require __DIR__ . '/compat/wordpress-6.6/compat.php'; require __DIR__ . '/compat/wordpress-6.6/resolve-patterns.php'; require __DIR__ . '/compat/wordpress-6.6/block-bindings/pattern-overrides.php'; diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index 1e380b9f5a296..a59bd0bd795e1 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -6,6 +6,8 @@ ### Breaking Changes +**Note** If you're using @wordpress/scripts for building JS scripts to target WordPress 6.5 or earlier, you should not upgrade to this version and continue using @wordpress/babel-preset-default@7. + - Use React's automatic runtime to transform JSX ([#61692](https://github.com/WordPress/gutenberg/pull/61692)). - Increase the minimum required Node.js version to v18.12.0 matching long-term support releases ([#31270](https://github.com/WordPress/gutenberg/pull/61930)). Learn more about [Node.js releases](https://nodejs.org/en/about/previous-releases). diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 691fff09fb3b5..0e3dddf70632b 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -86,13 +86,12 @@ $z-layers: ( // Show sidebar above wp-admin navigation bar for mobile viewports: // #wpadminbar { z-index: 99999 } ".interface-interface-skeleton__sidebar": 100000, - ".edit-post-layout__toggle-sidebar-panel": 100000, ".editor-layout__toggle-sidebar-panel": 100000, ".edit-widgets-sidebar": 100000, - ".edit-post-layout .edit-post-post-publish-panel": 100001, + ".editor-post-publish-panel": 100001, // For larger views, the wp-admin navbar dropdown should be at top of // the Publish Post sidebar. - ".edit-post-layout .edit-post-post-publish-panel {greater than small}": 99998, + ".editor-post-publish-panel {greater than small}": 99998, // For larger views, the wp-admin navbar dropdown should be on top of // the multi-entity saving sidebar. diff --git a/packages/block-editor/src/components/block-bindings-toolbar-indicator/index.js b/packages/block-editor/src/components/block-bindings-toolbar-indicator/index.js index bc04593f7ed2a..e25c489c1dbf9 100644 --- a/packages/block-editor/src/components/block-bindings-toolbar-indicator/index.js +++ b/packages/block-editor/src/components/block-bindings-toolbar-indicator/index.js @@ -57,7 +57,9 @@ export default function BlockBindingsToolbarIndicator( { clientIds } ) { isConnectedToPatternOverrides: getBlocksByClientId( clientIds ).some( ( block ) => - Object.values( block?.attributes.metadata?.bindings ).some( + Object.values( + block?.attributes?.metadata?.bindings || {} + ).some( ( binding ) => binding.source === 'core/pattern-overrides' ) diff --git a/packages/block-editor/src/components/block-canvas/style.scss b/packages/block-editor/src/components/block-canvas/style.scss index 4317672319544..9e924cb79bace 100644 --- a/packages/block-editor/src/components/block-canvas/style.scss +++ b/packages/block-editor/src/components/block-canvas/style.scss @@ -1,7 +1,7 @@ iframe[name="editor-canvas"] { + box-sizing: border-box; width: 100%; height: 100%; display: block; - background-color: $gray-300; - box-sizing: border-box; + background-color: transparent; } diff --git a/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js b/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js index 6b9e694c1cdf8..8db23267eee8f 100644 --- a/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js +++ b/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js @@ -8,7 +8,7 @@ import { parse, } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; -import { useCallback } from '@wordpress/element'; +import { useCallback, useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -25,13 +25,18 @@ import { withRootClientIdOptionKey } from '../../../store/utils'; * @return {Array} Returns the block types state. (block types, categories, collections, onSelect handler) */ const useBlockTypesState = ( rootClientId, onInsert, isQuick ) => { + const options = useMemo( + () => ( { [ withRootClientIdOptionKey ]: ! isQuick } ), + [ isQuick ] + ); const [ items ] = useSelect( ( select ) => [ - select( blockEditorStore ).getInserterItems( rootClientId, { - [ withRootClientIdOptionKey ]: ! isQuick, - } ), + select( blockEditorStore ).getInserterItems( + rootClientId, + options + ), ], - [ rootClientId, isQuick ] + [ rootClientId, options ] ); const [ categories, collections ] = useSelect( ( select ) => { diff --git a/packages/block-editor/src/components/provider/use-block-sync.js b/packages/block-editor/src/components/provider/use-block-sync.js index 300c108a70cf1..4e9cc9784554f 100644 --- a/packages/block-editor/src/components/provider/use-block-sync.js +++ b/packages/block-editor/src/components/provider/use-block-sync.js @@ -9,7 +9,6 @@ import { cloneBlock } from '@wordpress/blocks'; * Internal dependencies */ import { store as blockEditorStore } from '../../store'; -import { undoIgnoreBlocks } from '../../store/undo-ignore'; const noop = () => {}; @@ -274,10 +273,6 @@ export default function useBlockSync( { const updateParent = isPersistent ? onChangeRef.current : onInputRef.current; - const undoIgnore = undoIgnoreBlocks.has( blocks ); - if ( undoIgnore ) { - undoIgnoreBlocks.delete( blocks ); - } updateParent( blocks, { selection: { selectionStart: getSelectionStart(), @@ -285,7 +280,6 @@ export default function useBlockSync( { initialPosition: getSelectedBlocksInitialCaretPosition(), }, - undoIgnore, } ); } previousAreBlocksDifferent = areBlocksDifferent; diff --git a/packages/block-editor/src/components/spacing-sizes-control/hooks/use-spacing-sizes.js b/packages/block-editor/src/components/spacing-sizes-control/hooks/use-spacing-sizes.js index fcd4e3fb964a6..9943978c15af1 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/hooks/use-spacing-sizes.js +++ b/packages/block-editor/src/components/spacing-sizes-control/hooks/use-spacing-sizes.js @@ -42,7 +42,19 @@ export default function useSpacingSizes() { ...customSizes, ...themeSizes, ...defaultSizes, - ].sort( ( a, b ) => compare( a.slug, b.slug ) ); + ]; + + // Only sort if more than one origin has presets defined in order to + // preserve order for themes that don't include default presets and + // want a custom order. + if ( + ( customSizes.length && 1 ) + + ( themeSizes.length && 1 ) + + ( defaultSizes.length && 1 ) > + 1 + ) { + sizes.sort( ( a, b ) => compare( a.slug, b.slug ) ); + } return sizes.length > RANGE_CONTROL_MAX_SIZE ? [ @@ -53,8 +65,6 @@ export default function useSpacingSizes() { }, ...sizes, ] - : // See https://github.com/WordPress/gutenberg/pull/44247 for reasoning - // to use the index as the name in the range control. - sizes.map( ( { slug, size }, i ) => ( { name: i, slug, size } ) ); + : sizes; }, [ customSizes, themeSizes, defaultSizes ] ); } diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js index 28a7b1da98f73..5e4e0c7a222b0 100644 --- a/packages/block-editor/src/store/private-actions.js +++ b/packages/block-editor/src/store/private-actions.js @@ -6,7 +6,6 @@ import { Platform } from '@wordpress/element'; /** * Internal dependencies */ -import { undoIgnoreBlocks } from './undo-ignore'; import { store as blockEditorStore } from './index'; import { unlock } from '../lock-unlock'; @@ -292,34 +291,6 @@ export function deleteStyleOverride( id ) { }; } -/** - * A higher-order action that mark every change inside a callback as "non-persistent" - * and ignore pushing to the undo history stack. It's primarily used for synchronized - * derived updates from the block editor without affecting the undo history. - * - * @param {() => void} callback The synchronous callback to derive updates. - */ -export function syncDerivedUpdates( callback ) { - return ( { dispatch, select, registry } ) => { - registry.batch( () => { - // Mark every change in the `callback` as non-persistent. - dispatch( { - type: 'SET_EXPLICIT_PERSISTENT', - isPersistentChange: false, - } ); - callback(); - dispatch( { - type: 'SET_EXPLICIT_PERSISTENT', - isPersistentChange: undefined, - } ); - - // Ignore pushing undo stack for the updated blocks. - const updatedBlocks = select.getBlocks(); - undoIgnoreBlocks.add( updatedBlocks ); - } ); - }; -} - /** * Action that sets the element that had focus when focus leaves the editor canvas. * diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index bf7b5125a770e..bf98521dfe9b6 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -76,6 +76,8 @@ const EMPTY_ARRAY = []; */ const EMPTY_SET = new Set(); +const EMPTY_OBJECT = {}; + /** * Returns a block's name given its client ID, or null if no block exists with * the client ID. @@ -1996,7 +1998,7 @@ const buildBlockTypeItem = */ export const getInserterItems = createRegistrySelector( ( select ) => createSelector( - ( state, rootClientId = null, options = {} ) => { + ( state, rootClientId = null, options = EMPTY_OBJECT ) => { const buildReusableBlockInserterItem = ( reusableBlock ) => { const icon = ! reusableBlock.wp_pattern_sync_status ? { diff --git a/packages/block-editor/src/store/undo-ignore.js b/packages/block-editor/src/store/undo-ignore.js deleted file mode 100644 index f0a64428ea7c2..0000000000000 --- a/packages/block-editor/src/store/undo-ignore.js +++ /dev/null @@ -1,4 +0,0 @@ -// Keep track of the blocks that should not be pushing an additional -// undo stack when editing the entity. -// See the implementation of `syncDerivedUpdates` and `useBlockSync`. -export const undoIgnoreBlocks = new WeakSet(); diff --git a/packages/block-library/src/missing/edit.native.js b/packages/block-library/src/missing/edit.native.js index 7432f5fae0f12..950d9afaaebf3 100644 --- a/packages/block-library/src/missing/edit.native.js +++ b/packages/block-library/src/missing/edit.native.js @@ -1,12 +1,7 @@ /** * External dependencies */ -import { - View, - Text, - TouchableWithoutFeedback, - TouchableOpacity, -} from 'react-native'; +import { View, Text, TouchableOpacity } from 'react-native'; /** * WordPress dependencies @@ -25,6 +20,7 @@ import { store as blockEditorStore, } from '@wordpress/block-editor'; import { store as noticesStore } from '@wordpress/notices'; +import { requestUnsupportedBlockFallback } from '@wordpress/react-native-bridge'; /** * Internal dependencies @@ -48,11 +44,38 @@ export class UnsupportedBlockEdit extends Component { } toggleSheet() { + const { attributes, block, clientId } = this.props; + const { originalName } = attributes; + const title = this.getTitle(); + const blockContent = serialize( block ? [ block ] : [] ); + + if ( this.canEditUnsupportedBlock() ) { + requestUnsupportedBlockFallback( + blockContent, + clientId, + originalName, + title + ); + return; + } + this.setState( { showHelp: ! this.state.showHelp, } ); } + canEditUnsupportedBlock() { + const { + canEnableUnsupportedBlockEditor, + isUnsupportedBlockEditorSupported, + } = this.props; + + return ( + ! canEnableUnsupportedBlockEditor && + isUnsupportedBlockEditorSupported + ); + } + closeSheet() { this.setState( { showHelp: false, @@ -186,7 +209,11 @@ export class UnsupportedBlockEdit extends Component { ); const subtitle = ( - { __( 'Unsupported' ) } + + { this.canEditUnsupportedBlock() + ? __( 'Tap to edit' ) + : __( 'Unsupported' ) } + ); const icon = blockType @@ -198,8 +225,8 @@ export class UnsupportedBlockEdit extends Component { ); const iconClassName = 'unsupported-icon' + '-' + preferredColorScheme; return ( - - { this.renderHelpIcon() } + { ! this.canEditUnsupportedBlock() && + this.renderHelpIcon() } - + ); } } diff --git a/packages/block-library/src/missing/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/missing/test/__snapshots__/edit.native.js.snap index 245410a5c5d57..2ac371120be4b 100644 --- a/packages/block-library/src/missing/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/missing/test/__snapshots__/edit.native.js.snap @@ -9,12 +9,21 @@ exports[`Missing block renders without crashing 1`] = ` { "busy": undefined, "checked": undefined, - "disabled": true, + "disabled": undefined, "expanded": undefined, "selected": undefined, } } + accessibilityValue={ + { + "max": undefined, + "min": undefined, + "now": undefined, + "text": undefined, + } + } accessible={true} + collapsable={false} focusable={true} onClick={[Function]} onResponderGrant={[Function]} @@ -23,72 +32,79 @@ exports[`Missing block renders without crashing 1`] = ` onResponderTerminate={[Function]} onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} + style={ + { + "opacity": 1, + } + } > - + - - Path - - - - - Path - + + Path + + + + + Path + + + missing/block/title + + - missing/block/title + Unsupported - - Unsupported - `; diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss index 107fb6e6de5fd..9f22262c53bd9 100644 --- a/packages/block-library/src/navigation/editor.scss +++ b/packages/block-library/src/navigation/editor.scss @@ -468,10 +468,6 @@ $color-control-label-height: 20px; top: $admin-bar-height + $header-height + $block-toolbar-height + $border-width; } -.is-sidebar-opened .wp-block-navigation__responsive-container.is-menu-open { - right: $sidebar-width; -} - // When fullscreen. .is-fullscreen-mode { .wp-block-navigation__responsive-container.is-menu-open { diff --git a/packages/blocks/src/api/raw-handling/image-corrector.native.js b/packages/blocks/src/api/raw-handling/image-corrector.native.js index c6a9288ede2d3..550c2e0e6e153 100644 --- a/packages/blocks/src/api/raw-handling/image-corrector.native.js +++ b/packages/blocks/src/api/raw-handling/image-corrector.native.js @@ -10,7 +10,10 @@ export default function imageCorrector( node ) { return; } - if ( node.src.indexOf( 'file:' ) === 0 ) { + // For local files makes sure the path doesn't end with an invalid extension. + // This scenario often happens with content from MS Word and similar text apps. + // We still need to support local files pasted from the users Media library. + if ( node.src.startsWith( 'file:' ) && node.src.slice( -1 ) === '/' ) { node.setAttribute( 'src', '' ); } diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 4ad4dd4ec6312..fd89e5e01fd0f 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +### Enhancements + +- Add `text-wrap: balance` fallback to all instances of `text-wrap: pretty` for greater cross browser compatibility. ([#62233](https://github.com/WordPress/gutenberg/pull/62233)) + +### Bug Fixes + +- `Tabs`: Prevent accidental overflow in indicator ([#61979](https://github.com/WordPress/gutenberg/pull/61979)). + ## 28.0.0 (2024-05-31) ### Breaking Changes diff --git a/packages/components/src/checkbox-control/stories/index.story.tsx b/packages/components/src/checkbox-control/stories/index.story.tsx index 6752ce07667cb..2dbc14c8cde11 100644 --- a/packages/components/src/checkbox-control/stories/index.story.tsx +++ b/packages/components/src/checkbox-control/stories/index.story.tsx @@ -142,6 +142,8 @@ export const WithCustomLabel: StoryFn< typeof CheckboxControl > = ( { setChecked( v ); onChange( v ); } } + // Disable reason: For simplicity of the code snippet. + // eslint-disable-next-line no-restricted-syntax id="my-checkbox-with-custom-label" aria-describedby="my-custom-description" /> @@ -149,6 +151,7 @@ export const WithCustomLabel: StoryFn< typeof CheckboxControl > = ( { + { /* eslint-disable-next-line no-restricted-syntax */ }
A custom description.
diff --git a/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap b/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap index 73c8ebcfb4927..608cadbcf544f 100644 --- a/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap +++ b/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap @@ -68,6 +68,7 @@ exports[`DimensionControl rendering renders with custom sizes 1`] = ` color: #1e1e1e; line-height: 1.4; margin: 0; + text-wrap: balance; text-wrap: pretty; font-size: calc((13 / 13) * 13px); font-weight: normal; @@ -347,6 +348,7 @@ exports[`DimensionControl rendering renders with defaults 1`] = ` color: #1e1e1e; line-height: 1.4; margin: 0; + text-wrap: balance; text-wrap: pretty; font-size: calc((13 / 13) * 13px); font-weight: normal; @@ -636,6 +638,7 @@ exports[`DimensionControl rendering renders with icon and custom icon label 1`] color: #1e1e1e; line-height: 1.4; margin: 0; + text-wrap: balance; text-wrap: pretty; font-size: calc((13 / 13) * 13px); font-weight: normal; @@ -937,6 +940,7 @@ exports[`DimensionControl rendering renders with icon and default icon label 1`] color: #1e1e1e; line-height: 1.4; margin: 0; + text-wrap: balance; text-wrap: pretty; font-size: calc((13 / 13) * 13px); font-weight: normal; diff --git a/packages/components/src/heading/test/__snapshots__/index.tsx.snap b/packages/components/src/heading/test/__snapshots__/index.tsx.snap index 9a779613f56c2..cf863c4b2bb2e 100644 --- a/packages/components/src/heading/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/heading/test/__snapshots__/index.tsx.snap @@ -5,6 +5,7 @@ exports[`props should render correctly 1`] = ` color: #1e1e1e; line-height: 1.4; margin: 0; + text-wrap: balance; text-wrap: pretty; color: #1e1e1e; font-size: calc(1.95 * 13px); diff --git a/packages/components/src/tabs/tablist.tsx b/packages/components/src/tabs/tablist.tsx index cbd4290f06bff..917bcbe755ee0 100644 --- a/packages/components/src/tabs/tablist.tsx +++ b/packages/components/src/tabs/tablist.tsx @@ -53,10 +53,12 @@ function useTrackElementOffset( function updateIndicator( element: HTMLElement ) { setIndicatorPosition( { - left: element.offsetLeft, - top: element.offsetTop, - width: element.offsetWidth, - height: element.offsetHeight, + // Workaround to prevent unwanted scrollbars, see: + // https://github.com/WordPress/gutenberg/pull/61979 + left: Math.max( element.offsetLeft - 1, 0 ), + top: Math.max( element.offsetTop - 1, 0 ), + width: parseFloat( getComputedStyle( element ).width ), + height: parseFloat( getComputedStyle( element ).height ), } ); updateCallbackRef.current?.(); } diff --git a/packages/components/src/text/styles.ts b/packages/components/src/text/styles.ts index 1a0a6383363df..c7d4855279593 100644 --- a/packages/components/src/text/styles.ts +++ b/packages/components/src/text/styles.ts @@ -12,6 +12,7 @@ export const Text = css` color: ${ COLORS.gray[ 900 ] }; line-height: ${ CONFIG.fontLineHeightBase }; margin: 0; + text-wrap: balance; /* Fallback for Safari. */ text-wrap: pretty; `; diff --git a/packages/components/src/text/test/__snapshots__/index.tsx.snap b/packages/components/src/text/test/__snapshots__/index.tsx.snap index d2caecd9bf059..1b98c0853ac54 100644 --- a/packages/components/src/text/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/text/test/__snapshots__/index.tsx.snap @@ -22,6 +22,7 @@ exports[`Text should render highlighted words with highlightCaseSensitive 1`] = color: #1e1e1e; line-height: 1.4; margin: 0; + text-wrap: balance; text-wrap: pretty; font-size: calc((13 / 13) * 13px); font-weight: normal; @@ -54,6 +55,7 @@ exports[`Text snapshot tests should render correctly 1`] = ` color: #1e1e1e; line-height: 1.4; margin: 0; + text-wrap: balance; text-wrap: pretty; font-size: calc((13 / 13) * 13px); font-weight: normal; diff --git a/packages/core-data/src/entity-types/theme.ts b/packages/core-data/src/entity-types/theme.ts index 761c6461d4aca..75826c9110b65 100644 --- a/packages/core-data/src/entity-types/theme.ts +++ b/packages/core-data/src/entity-types/theme.ts @@ -129,6 +129,10 @@ declare module './base-entity-records' { * Custom font sizes if defined by the theme. */ 'editor-font-sizes': boolean | FontSize[]; + /** + * Custom spacing sizes if defined by the theme. + */ + 'editor-spacing-sizes': boolean | SpacingSize[]; /** * Custom gradient presets if defined by the theme. */ @@ -212,6 +216,12 @@ declare module './base-entity-records' { slug: string; } + export interface SpacingSize { + name: string; + size: number; + slug: string; + } + export interface GradientPreset { name: string; gradient: string; diff --git a/packages/create-block/lib/init-wp-scripts.js b/packages/create-block/lib/init-wp-scripts.js index ca0101957f47f..531eb970d8925 100644 --- a/packages/create-block/lib/init-wp-scripts.js +++ b/packages/create-block/lib/init-wp-scripts.js @@ -16,7 +16,7 @@ module.exports = async ( { slug } ) => { info( 'Installing `@wordpress/scripts` package. It might take a couple of minutes...' ); - await command( 'npm install @wordpress/scripts --save-dev', { + await command( 'npm install @wordpress/scripts@27 --save-dev', { cwd, } ); diff --git a/packages/customize-widgets/src/index.js b/packages/customize-widgets/src/index.js index 9afda775a1701..5de010fa8bd37 100644 --- a/packages/customize-widgets/src/index.js +++ b/packages/customize-widgets/src/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { createRoot } from '@wordpress/element'; +import { createRoot, StrictMode } from '@wordpress/element'; import { registerCoreBlocks, __experimentalGetCoreBlocks, @@ -92,11 +92,13 @@ export function initialize( editorName, blockEditorSettings ) { } ); createRoot( container ).render( - + + + ); } ); } diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss index bfff3786b1112..a2154790053d0 100644 --- a/packages/dataviews/src/style.scss +++ b/packages/dataviews/src/style.scss @@ -370,8 +370,7 @@ line-height: 16px; &:not(:empty) { - padding: $grid-unit-15 0; - padding-top: 0; + padding: 0 0 $grid-unit-15; } .dataviews-view-grid__field { @@ -603,11 +602,6 @@ } } -.dataviews-filters__custom-menu-radio-item-prefix { - display: block; - width: 24px; -} - .dataviews-bulk-edit-button.components-button { flex-shrink: 0; } diff --git a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md index 32c012ea3b644..2e152793334c0 100644 --- a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md +++ b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md @@ -6,6 +6,9 @@ ### Breaking Changes +**Note** If you're using @wordpress/scripts for building JS scripts to target WordPress 6.5 or earlier, you should not upgrade to this version and continue using @wordpress/dependency-extraction-webpack-plugin@5. + +- Use React's automatic runtime to transform JSX ([#61692](https://github.com/WordPress/gutenberg/pull/61692)). - Increase the minimum required Node.js version to v18.12.0 matching long-term support releases ([#31270](https://github.com/WordPress/gutenberg/pull/61930)). Learn more about [Node.js releases](https://nodejs.org/en/about/previous-releases). ## 5.9.0 (2024-05-16) diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 6603b1f685152..c152be045f129 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -20,7 +20,6 @@ import { privateApis as blockEditorPrivateApis, store as blockEditorStore, } from '@wordpress/block-editor'; -import { useViewportMatch } from '@wordpress/compose'; import { PluginArea } from '@wordpress/plugins'; import { __, sprintf } from '@wordpress/i18n'; import { useCallback, useMemo } from '@wordpress/element'; @@ -144,19 +143,16 @@ function useEditorStyles() { function Layout( { initialPost } ) { useCommands(); useEditPostCommands(); - const isWideViewport = useViewportMatch( 'large' ); const paddingAppenderRef = usePaddingAppender(); const shouldIframe = useShouldIframe(); const { createErrorNotice } = useDispatch( noticesStore ); const { mode, isFullscreenActive, - sidebarIsOpened, hasActiveMetaboxes, hasBlockSelected, showIconLabels, isDistractionFree, - showBlockBreadcrumbs, showMetaBoxes, hasHistory, isEditingTemplate, @@ -175,7 +171,6 @@ function Layout( { initialPost } ) { !! select( blockEditorStore ).getBlockSelectionStart(), showIconLabels: get( 'core', 'showIconLabels' ), isDistractionFree: get( 'core', 'distractionFree' ), - showBlockBreadcrumbs: get( 'core', 'showBlockBreadcrumbs' ), showMetaBoxes: select( editorStore ).getRenderingMode() === 'post-only', hasHistory: !! getEditorSettings().onNavigateToPreviousEntityRecord, @@ -201,11 +196,7 @@ function Layout( { initialPost } ) { } const className = clsx( 'edit-post-layout', 'is-mode-' + mode, { - 'is-sidebar-opened': sidebarIsOpened, 'has-metaboxes': hasActiveMetaboxes, - 'is-distraction-free': isDistractionFree && isWideViewport, - 'has-block-breadcrumbs': - showBlockBreadcrumbs && ! isDistractionFree && isWideViewport, } ); function onPluginAreaError( name ) { diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 0ec4388a9af70..e7600831f11ba 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -7,7 +7,7 @@ import { __experimentalRegisterExperimentalCoreBlocks, } from '@wordpress/block-library'; import deprecated from '@wordpress/deprecated'; -import { createRoot } from '@wordpress/element'; +import { createRoot, StrictMode } from '@wordpress/element'; import { dispatch, select } from '@wordpress/data'; import { store as preferencesStore } from '@wordpress/preferences'; import { @@ -137,12 +137,14 @@ export function initializeEditor( window.addEventListener( 'drop', ( e ) => e.preventDefault(), false ); root.render( - + + + ); return root; diff --git a/packages/edit-post/src/test/__snapshots__/editor.native.js.snap b/packages/edit-post/src/test/__snapshots__/editor.native.js.snap index 76bb42d5a2cce..4a88b249a1db6 100644 --- a/packages/edit-post/src/test/__snapshots__/editor.native.js.snap +++ b/packages/edit-post/src/test/__snapshots__/editor.native.js.snap @@ -1,5 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Editor adds empty image block when pasting unsupported HTML local image path 1`] = ` +" +
+" +`; + +exports[`Editor adds image block when pasting HTML local image path 1`] = ` +" +
+" +`; + exports[`Editor appends media correctly for allowed types 1`] = ` "
diff --git a/packages/edit-post/src/test/editor.native.js b/packages/edit-post/src/test/editor.native.js index acafc4d68d42a..8fe116758608b 100644 --- a/packages/edit-post/src/test/editor.native.js +++ b/packages/edit-post/src/test/editor.native.js @@ -9,8 +9,10 @@ import { getEditorHtml, getEditorTitle, initializeEditor, + pasteIntoRichText, screen, setupCoreBlocks, + within, } from 'test/helpers'; import { BackHandler } from 'react-native'; @@ -98,6 +100,38 @@ describe( 'Editor', () => { } ); } ); + it( 'adds empty image block when pasting unsupported HTML local image path', async () => { + await initializeEditor(); + await addBlock( screen, 'Paragraph' ); + + const paragraphBlock = getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphTextInput = + within( paragraphBlock ).getByPlaceholderText( 'Start writing…' ); + + pasteIntoRichText( paragraphTextInput, { + text: '
', + } ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'adds image block when pasting HTML local image path', async () => { + await initializeEditor(); + await addBlock( screen, 'Paragraph' ); + + const paragraphBlock = getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphTextInput = + within( paragraphBlock ).getByPlaceholderText( 'Start writing…' ); + + pasteIntoRichText( paragraphTextInput, { + files: [ 'file:///path/to/file.png' ], + } ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + it( 'appends media correctly for allowed types', async () => { // Arrange requestMediaImport diff --git a/packages/edit-site/src/components/block-editor/block-inspector-button.js b/packages/edit-site/src/components/block-editor/block-inspector-button.js deleted file mode 100644 index 0c669b21fda83..0000000000000 --- a/packages/edit-site/src/components/block-editor/block-inspector-button.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { speak } from '@wordpress/a11y'; -import { MenuItem } from '@wordpress/components'; -import { useSelect, useDispatch } from '@wordpress/data'; -import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; -import { privateApis as editorPrivateApis } from '@wordpress/editor'; - -/** - * Internal dependencies - */ -import { unlock } from '../../lock-unlock'; - -const { interfaceStore } = unlock( editorPrivateApis ); - -export default function BlockInspectorButton( { onClick = () => {} } ) { - const { shortcut, isBlockInspectorOpen } = useSelect( - ( select ) => ( { - shortcut: select( - keyboardShortcutsStore - ).getShortcutRepresentation( 'core/editor/toggle-sidebar' ), - isBlockInspectorOpen: - select( interfaceStore ).getActiveComplementaryArea( - 'core' - ) === 'edit-post/block', - } ), - [] - ); - const { enableComplementaryArea, disableComplementaryArea } = - useDispatch( interfaceStore ); - - const label = isBlockInspectorOpen - ? __( 'Hide more settings' ) - : __( 'Show more settings' ); - - return ( - { - if ( isBlockInspectorOpen ) { - disableComplementaryArea( 'core' ); - speak( __( 'Block settings closed' ) ); - } else { - enableComplementaryArea( 'core', 'edit-post/block' ); - speak( - __( - 'Additional settings are now available in the Editor block settings sidebar' - ) - ); - } - // Close dropdown menu. - onClick(); - } } - shortcut={ shortcut } - > - { label } - - ); -} diff --git a/packages/edit-site/src/components/block-editor/inserter-media-categories.js b/packages/edit-site/src/components/block-editor/inserter-media-categories.js deleted file mode 100644 index 7ebc771126122..0000000000000 --- a/packages/edit-site/src/components/block-editor/inserter-media-categories.js +++ /dev/null @@ -1,225 +0,0 @@ -/** - * The `edit-site` settings here need to be in sync with the corresponding ones in `site-editor` package. - * See `packages/edit-site/src/components/block-editor/inserter-media-categories.js`. - * - * In the future we could consider creating an Openvese package that can be used in both `editor` and `site-editor`. - * The rest of the settings would still need to be in sync though. - */ - -/** - * WordPress dependencies - */ -import { __, sprintf, _x } from '@wordpress/i18n'; -import { resolveSelect } from '@wordpress/data'; -import { decodeEntities } from '@wordpress/html-entities'; - -/** - * Internal dependencies - */ -import { store as coreStore } from '@wordpress/core-data'; - -/** @typedef {import('@wordpress/block-editor').InserterMediaRequest} InserterMediaRequest */ -/** @typedef {import('@wordpress/block-editor').InserterMediaItem} InserterMediaItem */ -/** @typedef {import('@wordpress/block-editor').InserterMediaCategory} InserterMediaCategory */ - -const getExternalLink = ( url, text ) => - `${ text }`; - -const getExternalLinkAttributes = ( url ) => - `href="${ url }" target="_blank" rel="noreferrer noopener"`; - -const getOpenverseLicense = ( license, licenseVersion ) => { - let licenseName = license.trim(); - // PDM has no abbreviation - if ( license !== 'pdm' ) { - licenseName = license.toUpperCase().replace( 'SAMPLING', 'Sampling' ); - } - // If version is known, append version to the name. - // The license has to have a version to be valid. Only - // PDM (public domain mark) doesn't have a version. - if ( licenseVersion ) { - licenseName += ` ${ licenseVersion }`; - } - // For licenses other than public-domain marks, prepend 'CC' to the name. - if ( ! [ 'pdm', 'cc0' ].includes( license ) ) { - licenseName = `CC ${ licenseName }`; - } - return licenseName; -}; - -const getOpenverseCaption = ( item ) => { - const { - title, - foreign_landing_url: foreignLandingUrl, - creator, - creator_url: creatorUrl, - license, - license_version: licenseVersion, - license_url: licenseUrl, - } = item; - const fullLicense = getOpenverseLicense( license, licenseVersion ); - const _creator = decodeEntities( creator ); - let _caption; - if ( _creator ) { - _caption = title - ? sprintf( - // translators: %1s: Title of a media work from Openverse; %2s: Name of the work's creator; %3s: Work's licence e.g: "CC0 1.0". - _x( '"%1$s" by %2$s/ %3$s', 'caption' ), - getExternalLink( - foreignLandingUrl, - decodeEntities( title ) - ), - creatorUrl - ? getExternalLink( creatorUrl, _creator ) - : _creator, - licenseUrl - ? getExternalLink( - `${ licenseUrl }?ref=openverse`, - fullLicense - ) - : fullLicense - ) - : sprintf( - // translators: %1s: Link attributes for a given Openverse media work; %2s: Name of the work's creator; %3s: Works's licence e.g: "CC0 1.0". - _x( 'Work by %2$s/ %3$s', 'caption' ), - getExternalLinkAttributes( foreignLandingUrl ), - creatorUrl - ? getExternalLink( creatorUrl, _creator ) - : _creator, - licenseUrl - ? getExternalLink( - `${ licenseUrl }?ref=openverse`, - fullLicense - ) - : fullLicense - ); - } else { - _caption = title - ? sprintf( - // translators: %1s: Title of a media work from Openverse; %2s: Work's licence e.g: "CC0 1.0". - _x( '"%1$s"/ %2$s', 'caption' ), - getExternalLink( - foreignLandingUrl, - decodeEntities( title ) - ), - licenseUrl - ? getExternalLink( - `${ licenseUrl }?ref=openverse`, - fullLicense - ) - : fullLicense - ) - : sprintf( - // translators: %1s: Link attributes for a given Openverse media work; %2s: Works's licence e.g: "CC0 1.0". - _x( 'Work/ %3$s', 'caption' ), - getExternalLinkAttributes( foreignLandingUrl ), - licenseUrl - ? getExternalLink( - `${ licenseUrl }?ref=openverse`, - fullLicense - ) - : fullLicense - ); - } - return _caption.replace( /\s{2}/g, ' ' ); -}; - -const coreMediaFetch = async ( query = {} ) => { - const mediaItems = await resolveSelect( coreStore ).getMediaItems( { - ...query, - orderBy: !! query?.search ? 'relevance' : 'date', - } ); - return mediaItems.map( ( mediaItem ) => ( { - ...mediaItem, - alt: mediaItem.alt_text, - url: mediaItem.source_url, - previewUrl: mediaItem.media_details?.sizes?.medium?.source_url, - caption: mediaItem.caption?.raw, - } ) ); -}; - -/** @type {InserterMediaCategory[]} */ -const inserterMediaCategories = [ - { - name: 'images', - labels: { - name: __( 'Images' ), - search_items: __( 'Search images' ), - }, - mediaType: 'image', - async fetch( query = {} ) { - return coreMediaFetch( { ...query, media_type: 'image' } ); - }, - }, - { - name: 'videos', - labels: { - name: __( 'Videos' ), - search_items: __( 'Search videos' ), - }, - mediaType: 'video', - async fetch( query = {} ) { - return coreMediaFetch( { ...query, media_type: 'video' } ); - }, - }, - { - name: 'audio', - labels: { - name: __( 'Audio' ), - search_items: __( 'Search audio' ), - }, - mediaType: 'audio', - async fetch( query = {} ) { - return coreMediaFetch( { ...query, media_type: 'audio' } ); - }, - }, - { - name: 'openverse', - labels: { - name: __( 'Openverse' ), - search_items: __( 'Search Openverse' ), - }, - mediaType: 'image', - async fetch( query = {} ) { - const defaultArgs = { - mature: false, - excluded_source: 'flickr,inaturalist,wikimedia', - license: 'pdm,cc0', - }; - const finalQuery = { ...query, ...defaultArgs }; - const mapFromInserterMediaRequest = { - per_page: 'page_size', - search: 'q', - }; - const url = new URL( 'https://api.openverse.org/v1/images/' ); - Object.entries( finalQuery ).forEach( ( [ key, value ] ) => { - const queryKey = mapFromInserterMediaRequest[ key ] || key; - url.searchParams.set( queryKey, value ); - } ); - const response = await window.fetch( url, { - headers: { - 'User-Agent': 'WordPress/inserter-media-fetch', - }, - } ); - const jsonResponse = await response.json(); - const results = jsonResponse.results; - return results.map( ( result ) => ( { - ...result, - // This is a temp solution for better titles, until Openverse API - // completes the cleaning up of some titles of their upstream data. - title: result.title?.toLowerCase().startsWith( 'file:' ) - ? result.title.slice( 5 ) - : result.title, - sourceId: result.id, - id: undefined, - caption: getOpenverseCaption( result ), - previewUrl: result.thumbnail, - } ) ); - }, - getReportUrl: ( { sourceId } ) => - `https://wordpress.org/openverse/image/${ sourceId }/report/`, - isExternalResource: true, - }, -]; - -export default inserterMediaCategories; diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 26b20b66f11c7..6f61e01c1d7c6 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -234,7 +234,7 @@ export default function Editor( { isLoading } ) { { const { getAllShortcutKeyCombinations } = select( keyboardShortcutsStore @@ -102,10 +101,6 @@ export default function Layout() { 'core', 'distractionFree' ), - hasBlockBreadcrumbs: select( preferencesStore ).get( - 'core', - 'showBlockBreadcrumbs' - ), hasBlockSelected: select( blockEditorStore ).getBlockSelectionStart(), }; @@ -185,10 +180,6 @@ export default function Layout() { 'is-full-canvas': canvasMode === 'edit', 'has-fixed-toolbar': hasFixedToolbar, 'is-block-toolbar-visible': hasBlockSelected, - 'has-block-breadcrumbs': - hasBlockBreadcrumbs && - ! isDistractionFree && - canvasMode === 'edit', } ) } > diff --git a/packages/edit-site/src/components/layout/style.scss b/packages/edit-site/src/components/layout/style.scss index 8c15cdae33881..0c5412b6d765b 100644 --- a/packages/edit-site/src/components/layout/style.scss +++ b/packages/edit-site/src/components/layout/style.scss @@ -4,6 +4,11 @@ color: $gray-400; display: flex; flex-direction: column; + + // Show a dark background in "frame" mode to avoid edge artifacts. + &:not(.is-full-canvas) .editor-visual-editor { + background: $gray-900; + } } .edit-site-layout__hub { diff --git a/packages/edit-site/src/components/pagination/index.js b/packages/edit-site/src/components/pagination/index.js index 2608621f39c84..450ff2a17d5b5 100644 --- a/packages/edit-site/src/components/pagination/index.js +++ b/packages/edit-site/src/components/pagination/index.js @@ -46,8 +46,7 @@ export default function Pagination( {