diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 76d1e45e3c6ce8..c469ccfd74b558 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,23 +4,21 @@ version: 2 updates: # Check for updates to GitHub Actions. - package-ecosystem: 'github-actions' - directory: '/' + directories: + - '.github/workflows' + - '.github/setup-node' schedule: interval: 'daily' open-pull-requests-limit: 10 labels: - 'GitHub Actions' - '[Type] Build Tooling' + ignore: + - dependency-name: 'actions/setup-java' + - dependency-name: 'gradle/*' + - dependency-name: 'reactivecircus/*' + - dependency-name: 'ruby/setup-ruby' groups: github-actions: patterns: - '*' - exclude-patterns: - - 'actions/setup-java' - - 'gradle/*' - - 'reactivecircus/*' - react-native: - patterns: - - 'actions/setup-java' - - 'gradle/*' - - 'reactivecircus/*' diff --git a/.github/setup-node/action.yml b/.github/setup-node/action.yml index a17adfe5f50071..83c4123fbab939 100644 --- a/.github/setup-node/action.yml +++ b/.github/setup-node/action.yml @@ -10,7 +10,7 @@ runs: using: 'composite' steps: - name: Use desired version of Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: node-version-file: '.nvmrc' node-version: ${{ inputs.node-version }} @@ -25,7 +25,7 @@ runs: - name: Cache node_modules id: cache-node_modules - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: '**/node_modules' key: node_modules-${{ runner.os }}-${{ runner.arch }}-${{ steps.node-version.outputs.NODE_VERSION }}-${{ hashFiles('package-lock.json') }} @@ -36,7 +36,7 @@ runs: npm ci shell: bash - name: Upload npm logs as an artifact on failure - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 if: failure() with: name: npm-logs diff --git a/.github/workflows/upload-release-to-plugin-repo.yml b/.github/workflows/upload-release-to-plugin-repo.yml index 8f823a543b826e..7d2c780599cddf 100644 --- a/.github/workflows/upload-release-to-plugin-repo.yml +++ b/.github/workflows/upload-release-to-plugin-repo.yml @@ -169,7 +169,7 @@ jobs: steps: - name: Install Subversion run: | - apt-get update -y && apt-get install -y subversion + sudo apt-get update -y && sudo apt-get install -y subversion - name: Check out Gutenberg trunk from WP.org plugin repo run: | @@ -228,7 +228,7 @@ jobs: steps: - name: Install Subversion run: | - apt-get update -y && apt-get install -y subversion + sudo apt-get update -y && sudo apt-get install -y subversion - name: Download and unzip Gutenberg plugin asset into tags folder env: diff --git a/backport-changelog/6.8/8228.md b/backport-changelog/6.8/8228.md new file mode 100644 index 00000000000000..0b3774ffffa5ba --- /dev/null +++ b/backport-changelog/6.8/8228.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/8228 + +* https://github.com/WordPress/gutenberg/pull/68970 diff --git a/backport-changelog/6.8/8245.md b/backport-changelog/6.8/8245.md new file mode 100644 index 00000000000000..ca9bc3588c5cec --- /dev/null +++ b/backport-changelog/6.8/8245.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/8245 + +* https://github.com/WordPress/gutenberg/pull/68983 diff --git a/backport-changelog/6.8/8265.md b/backport-changelog/6.8/8265.md new file mode 100644 index 00000000000000..46fa3f01810514 --- /dev/null +++ b/backport-changelog/6.8/8265.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/8265 + +* https://github.com/WordPress/gutenberg/pull/69057 diff --git a/backport-changelog/6.8/6910.md b/backport-changelog/6.9/6910.md similarity index 100% rename from backport-changelog/6.8/6910.md rename to backport-changelog/6.9/6910.md diff --git a/changelog.txt b/changelog.txt index e0ff271a5b28d8..e791d102a6b773 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,7 +1,15 @@ == Changelog == -= 20.2.0-rc.1 = += 20.0.1 = +## Changelog + +### Bug Fixes + +- iAPI Router: add missing changelog entry for [#68923](https://github.com/WordPress/gutenberg/pull/68945) + + += 20.2.0 = ## Changelog @@ -143,6 +151,8 @@ The following contributors merged PRs in this release: @afercia @benazeer-ben @fabiankaegy @himanshupathak95 @im3dabasia @Infinite-Null @justlevine @karthick-murugan @Mamaduka @Mayank-Tripathi32 @SainathPoojary @shail-mehta @shimotmk @Soean @spacedmonkey @stokesman @Sukhendu2002 @t-hamano @yogeshbhutkar + + = 20.1.0 = ## Changelog diff --git a/gutenberg.php b/gutenberg.php index 06152fa72876e1..de187b8d13af90 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.6 * Requires PHP: 7.2 - * Version: 20.2.0-rc.1 + * Version: 20.2.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/compat/wordpress-6.8/blocks.php b/lib/compat/wordpress-6.8/blocks.php index 90be78cd1457fb..2b0fcd19e200e1 100644 --- a/lib/compat/wordpress-6.8/blocks.php +++ b/lib/compat/wordpress-6.8/blocks.php @@ -217,3 +217,35 @@ function gutenberg_update_ignored_hooked_blocks_postmeta( $post ) { add_filter( 'rest_pre_insert_page', 'gutenberg_update_ignored_hooked_blocks_postmeta' ); add_filter( 'rest_pre_insert_post', 'gutenberg_update_ignored_hooked_blocks_postmeta' ); add_filter( 'rest_pre_insert_wp_block', 'gutenberg_update_ignored_hooked_blocks_postmeta' ); + +/** + * Update Query `parents` argument validation for hierarchical post types. + * A zero is a valid parent ID for hierarchical post types. Used to display top-level items. + * + * Add new handler for `sticky` query argument. + * + * @param array $query The query vars. + * @param WP_Block $block Block instance. + * @return array The filtered query vars. + */ +function gutenberg_update_query_vars_from_query_block_6_8( $query, $block ) { + if ( ! empty( $block->context['query']['parents'] ) && is_post_type_hierarchical( $query['post_type'] ) ) { + $query['post_parent__in'] = array_unique( array_map( 'intval', $block->context['query']['parents'] ) ); + } + + if ( isset( $block->context['query']['sticky'] ) && ! empty( $block->context['query']['sticky'] ) ) { + if ( 'ignore' === $block->context['query']['sticky'] ) { + $sticky = get_option( 'sticky_posts' ); + + /** + * The core will set `post__not_in` because it asserts that any sticky value other than `only` is `exclude`. + * Let's override that while supporting any `post__not_in` values outside sticky post logic. + */ + $query['post__not_in'] = array_diff( $query['post__not_in'], ! empty( $sticky ) ? $sticky : array() ); + $query['ignore_sticky_posts'] = 1; + } + } + + return $query; +} +add_filter( 'query_loop_block_query_vars', 'gutenberg_update_query_vars_from_query_block_6_8', 10, 2 ); diff --git a/lib/compat/wordpress-6.8/rest-api.php b/lib/compat/wordpress-6.8/rest-api.php index cc3d3e89014e93..fc68077b67f765 100644 --- a/lib/compat/wordpress-6.8/rest-api.php +++ b/lib/compat/wordpress-6.8/rest-api.php @@ -48,7 +48,6 @@ function gutenberg_add_default_template_part_areas_to_index( WP_REST_Response $r $response->data['default_template_part_areas'] = get_allowed_block_template_part_areas(); return $response; } - add_filter( 'rest_index', 'gutenberg_add_default_template_part_areas_to_index' ); /** @@ -70,5 +69,48 @@ function gutenberg_add_default_template_types_to_index( WP_REST_Response $respon $response->data['default_template_types'] = $indexed_template_types; return $response; } - add_filter( 'rest_index', 'gutenberg_add_default_template_types_to_index' ); + +/** + * Adds `ignore_sticky` parameter to the post collection endpoint. + * + * Note: Backports into the wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php file. + * + * @param array $query_params JSON Schema-formatted collection parameters. + * @param WP_Post_Type $post_type Post type object. + * @return array + */ +function gutenberg_modify_post_collection_paramt( $query_params, WP_Post_Type $post_type ) { + if ( 'post' === $post_type->name && ! isset( $query_params['ignore_sticky'] ) ) { + $query_params['ignore_sticky'] = array( + 'description' => __( 'Whether to ignore sticky posts or not.' ), + 'type' => 'boolean', + 'default' => false, + ); + } + + return $query_params; +} +add_filter( 'rest_post_collection_params', 'gutenberg_modify_post_collection_paramt', 10, 2 ); + +/** + * Modify posts query based on `ignore_sticky` parameter. + * + * Note: Backports into the wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php file. + * + * @param array $prepared_args Array of arguments for WP_User_Query. + * @param WP_REST_Request $request The REST API request. + * @return array Modified arguments + */ +function gutenberg_modify_post_collection_query( $args, WP_REST_Request $request ) { + /* + * Honor the original REST API `post__in` behavior. Don't prepend sticky posts + * when `post__in` has been specified. + */ + if ( isset( $request['ignore_sticky'] ) && empty( $args['post__in'] ) ) { + $args['ignore_sticky_posts'] = $request['ignore_sticky']; + } + + return $args; +} +add_filter( 'rest_post_query', 'gutenberg_modify_post_collection_query', 10, 2 ); diff --git a/lib/experimental/navigation-theme-opt-in.php b/lib/experimental/navigation-theme-opt-in.php index 547aa8b88ea7d1..27dbabaf234486 100644 --- a/lib/experimental/navigation-theme-opt-in.php +++ b/lib/experimental/navigation-theme-opt-in.php @@ -29,6 +29,8 @@ * * @see https://core.trac.wordpress.org/ticket/50544 * + * @global WP_Customize_Manager $wp_customize + * * @param int $menu_id ID of the updated menu. * @param int $menu_item_db_id ID of the new menu item. * @param array $args An array of arguments used to update/add the menu item. diff --git a/package-lock.json b/package-lock.json index 8c8ec35e2c3a99..38b92c08b9a792 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "20.2.0-rc.1", + "version": "20.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "20.2.0-rc.1", + "version": "20.2.0", "hasInstallScript": true, "license": "GPL-2.0-or-later", "workspaces": [ @@ -45328,9 +45328,9 @@ } }, "node_modules/undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "version": "5.28.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz", + "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==", "license": "MIT", "dependencies": { "@fastify/busboy": "^2.0.0" @@ -49960,7 +49960,7 @@ }, "packages/block-directory": { "name": "@wordpress/block-directory", - "version": "5.17.0", + "version": "5.17.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -51024,7 +51024,7 @@ }, "packages/edit-post": { "name": "@wordpress/edit-post", - "version": "8.17.0", + "version": "8.17.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -51071,7 +51071,7 @@ }, "packages/edit-site": { "name": "@wordpress/edit-site", - "version": "6.17.0", + "version": "6.17.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -51177,7 +51177,7 @@ }, "packages/editor": { "name": "@wordpress/editor", - "version": "14.17.0", + "version": "14.17.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", diff --git a/package.json b/package.json index b5fd51d6edd49b..3d0194fb941bb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "20.2.0-rc.1", + "version": "20.2.0", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", diff --git a/packages/api-fetch/src/middlewares/preloading.js b/packages/api-fetch/src/middlewares/preloading.js index ac293738513bd3..3eaab48dc8392c 100644 --- a/packages/api-fetch/src/middlewares/preloading.js +++ b/packages/api-fetch/src/middlewares/preloading.js @@ -68,15 +68,40 @@ function createPreloadingMiddleware( preloadedData ) { * @return {Promise} Promise with the response. */ function prepareResponse( responseData, parse ) { - return Promise.resolve( - parse - ? responseData.body - : new window.Response( JSON.stringify( responseData.body ), { - status: 200, - statusText: 'OK', - headers: responseData.headers, - } ) - ); + if ( parse ) { + return Promise.resolve( responseData.body ); + } + + try { + return Promise.resolve( + new window.Response( JSON.stringify( responseData.body ), { + status: 200, + statusText: 'OK', + headers: responseData.headers, + } ) + ); + } catch { + // See: https://github.com/WordPress/gutenberg/issues/67358#issuecomment-2621163926. + Object.entries( responseData.headers ).forEach( ( [ key, value ] ) => { + if ( key.toLowerCase() === 'link' ) { + responseData.headers[ key ] = value.replace( + /<([^>]+)>/, + ( /** @type {any} */ _, /** @type {string} */ url ) => + `<${ encodeURI( url ) }>` + ); + } + } ); + + return Promise.resolve( + parse + ? responseData.body + : new window.Response( JSON.stringify( responseData.body ), { + status: 200, + statusText: 'OK', + headers: responseData.headers, + } ) + ); + } } export default createPreloadingMiddleware; diff --git a/packages/api-fetch/src/middlewares/test/preloading.js b/packages/api-fetch/src/middlewares/test/preloading.js index 696d8a37648651..a3ffedbdef46d5 100644 --- a/packages/api-fetch/src/middlewares/test/preloading.js +++ b/packages/api-fetch/src/middlewares/test/preloading.js @@ -265,6 +265,57 @@ describe( 'Preloading Middleware', () => { expect( secondMiddleware ).toHaveBeenCalledTimes( 1 ); } ); + it( 'should not throw an error when non-ASCII headers are present', async () => { + const noResponseMock = 'undefined' === typeof window.Response; + if ( noResponseMock ) { + window.Response = class { + constructor( body, options ) { + this.body = JSON.parse( body ); + this.headers = options.headers; + + // Check for non-ASCII characters in headers + for ( const [ key, value ] of Object.entries( + this.headers || {} + ) ) { + if ( /[^\x00-\x7F]/.test( value ) ) { + throw new Error( + `Invalid non-ASCII character found in header: ${ key }` + ); + } + } + } + }; + } + + const data = { + body: 'Hello', + headers: { + Link: '; rel="alternate"; type=text/html', + }, + }; + + const preloadedData = { + 'wp/v2/example': data, + }; + + const preloadingMiddleware = + createPreloadingMiddleware( preloadedData ); + + const requestOptions = { + method: 'GET', + path: 'wp/v2/example', + parse: false, + }; + + await expect( + preloadingMiddleware( requestOptions, () => {} ) + ).resolves.not.toThrow(); + + if ( noResponseMock ) { + delete window.Response; + } + } ); + describe.each( [ [ 'GET' ], [ 'OPTIONS' ] ] )( '%s', ( method ) => { describe.each( [ [ 'all empty', {} ], diff --git a/packages/babel-preset-default/polyfill-exclusions.js b/packages/babel-preset-default/polyfill-exclusions.js index ca8c045d124146..9d0d18737540cb 100644 --- a/packages/babel-preset-default/polyfill-exclusions.js +++ b/packages/babel-preset-default/polyfill-exclusions.js @@ -28,4 +28,9 @@ module.exports = [ // // @see https://github.com/WordPress/gutenberg/pull/67230 /^es(next)?\.set\./, + // Remove Iterator feature polyfills. + // For the same reasoning as for Set exlusion above, we're excluding all iterator helper polyfills. + // + // @see https://github.com/WordPress/wordpress-develop/pull/8224#issuecomment-2636390007. + /^es(next)?\.iterator\./, ]; diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index b3af341dba0f37..f7750c2e49f170 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-directory", - "version": "5.17.0", + "version": "5.17.1", "description": "Extend editor with block directory features to search, download and install blocks.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-directory/src/plugins/index.js b/packages/block-directory/src/plugins/index.js index 283ff0ea4d2715..50418a9944d311 100644 --- a/packages/block-directory/src/plugins/index.js +++ b/packages/block-directory/src/plugins/index.js @@ -13,6 +13,9 @@ import InstalledBlocksPrePublishPanel from './installed-blocks-pre-publish-panel import getInstallMissing from './get-install-missing'; registerPlugin( 'block-directory', { + // The icon is explicitly set to undefined to prevent PluginPrePublishPanel + // from rendering the fallback icon pluginIcon. + icon: undefined, render() { return ( <> diff --git a/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js b/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js index 17630c9904e8bd..233f3261c79fc5 100644 --- a/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js +++ b/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js @@ -3,7 +3,6 @@ */ import { _n, sprintf } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; -import { blockDefault } from '@wordpress/icons'; import { PluginPrePublishPanel } from '@wordpress/editor'; /** @@ -24,7 +23,6 @@ export default function InstalledBlocksPrePublishPanel() { return ( ( ); +const renderToggle = + ( duotone, resetDuotone ) => + ( { onToggle, isOpen } ) => { + const duotoneButtonRef = useRef( undefined ); + + const toggleProps = { + onClick: onToggle, + className: clsx( { 'is-open': isOpen } ), + 'aria-expanded': isOpen, + ref: duotoneButtonRef, + }; + + const removeButtonProps = { + onClick: () => { + if ( isOpen ) { + onToggle(); + } + resetDuotone(); + // Return focus to parent button. + duotoneButtonRef.current?.focus(); + }, + className: 'block-editor-panel-duotone-settings__reset', + label: __( 'Reset' ), + }; + + return ( + <> + + { duotone && ( + diff --git a/packages/block-editor/src/components/global-styles/style.scss b/packages/block-editor/src/components/global-styles/style.scss index 5684cf9c0d3034..3645586f283115 100644 --- a/packages/block-editor/src/components/global-styles/style.scss +++ b/packages/block-editor/src/components/global-styles/style.scss @@ -58,6 +58,11 @@ } } +.block-editor-global-styles-filters-panel__dropdown { + border: 1px solid $gray-300; + border-radius: $radius-small; +} + // These styles are similar to the color palette. .block-editor-global-styles__shadow-indicator { appearance: none; @@ -100,3 +105,25 @@ /*rtl:ignore*/ direction: ltr; } + +.block-editor-panel-duotone-settings__reset { + position: absolute; + right: 0; + top: $grid-unit; + margin: auto $grid-unit auto; + opacity: 0; + @media not (prefers-reduced-motion) { + transition: opacity 0.1s ease-in-out; + } + + .block-editor-global-styles-filters-panel__dropdown:hover &, + &:focus, + &:hover { + opacity: 1; + } + + @media (hover: none) { + // Show reset button on devices that do not support hover. + opacity: 1; + } +} diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js index 498030a0019dcc..baedf91b87a906 100644 --- a/packages/block-editor/src/components/inserter/quick-inserter.js +++ b/packages/block-editor/src/components/inserter/quick-inserter.js @@ -21,6 +21,7 @@ import { store as blockEditorStore } from '../../store'; const SEARCH_THRESHOLD = 6; const SHOWN_BLOCK_TYPES = 6; +const SHOWN_BLOCK_PATTERNS = 2; export default function QuickInserter( { onSelect, @@ -106,7 +107,9 @@ export default function QuickInserter( { rootClientId={ rootClientId } clientId={ clientId } isAppender={ isAppender } - maxBlockPatterns={ 0 } + maxBlockPatterns={ + !! filterValue ? SHOWN_BLOCK_PATTERNS : 0 + } maxBlockTypes={ SHOWN_BLOCK_TYPES } isDraggable={ false } selectBlockOnInsert={ selectBlockOnInsert } diff --git a/packages/block-editor/src/components/inserter/search-results.js b/packages/block-editor/src/components/inserter/search-results.js index 5a5725a3bb08cd..c020e86f99a638 100644 --- a/packages/block-editor/src/components/inserter/search-results.js +++ b/packages/block-editor/src/components/inserter/search-results.js @@ -84,7 +84,9 @@ function InserterSearchResults( { ] = useBlockTypesState( destinationRootClientId, onInsertBlocks, isQuick ); const [ patterns, , onClickPattern ] = usePatternsState( onInsertBlocks, - destinationRootClientId + destinationRootClientId, + undefined, + isQuick ); const filteredBlockPatterns = useMemo( () => { diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index fe0893b0619a72..20aa1c288103d4 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -168,10 +168,6 @@ $block-inserter-tabs-height: 44px; text-align: center; } -.block-editor-inserter__no-results-icon { - fill: $gray-600; -} - .block-editor-inserter__child-blocks { padding: 0 $grid-unit-20; } diff --git a/packages/block-editor/src/components/keyboard-shortcuts/index.js b/packages/block-editor/src/components/keyboard-shortcuts/index.js index fcc2cea4043753..4d368e6c1d0ab4 100644 --- a/packages/block-editor/src/components/keyboard-shortcuts/index.js +++ b/packages/block-editor/src/components/keyboard-shortcuts/index.js @@ -29,7 +29,7 @@ function KeyboardShortcutsRegister() { category: 'block', description: __( 'Remove the selected block(s).' ), keyCombination: { - modifier: 'shift', + modifier: 'primaryShift', character: 'backspace', }, } ); diff --git a/packages/block-editor/src/components/rich-text/event-listeners/delete.js b/packages/block-editor/src/components/rich-text/event-listeners/delete.js index 8373ca3c9f72ae..ad59ae8a70d322 100644 --- a/packages/block-editor/src/components/rich-text/event-listeners/delete.js +++ b/packages/block-editor/src/components/rich-text/event-listeners/delete.js @@ -6,7 +6,7 @@ import { isCollapsed, isEmpty } from '@wordpress/rich-text'; export default ( props ) => ( element ) => { function onKeyDown( event ) { - const { keyCode, shiftKey } = event; + const { keyCode, shiftKey, ctrlKey, metaKey } = event; if ( event.defaultPrevented ) { return; @@ -30,8 +30,8 @@ export default ( props ) => ( element ) => { return; } - // Exclude shift+backspace as they are shortcuts for deleting blocks. - if ( shiftKey ) { + // Exclude (command|ctrl)+shift+backspace as they are shortcuts for deleting blocks. + if ( shiftKey && ( ctrlKey || metaKey ) ) { return; } diff --git a/packages/block-editor/src/components/writing-flow/use-tab-nav.js b/packages/block-editor/src/components/writing-flow/use-tab-nav.js index 46c40d56fe96d9..7f06cfe651db3e 100644 --- a/packages/block-editor/src/components/writing-flow/use-tab-nav.js +++ b/packages/block-editor/src/components/writing-flow/use-tab-nav.js @@ -125,6 +125,10 @@ export default function useTabNav() { return; } + if ( ! hasMultiSelection() && ! getSelectedBlockClientId() ) { + return; + } + const isShift = event.shiftKey; const direction = isShift ? 'findPrevious' : 'findNext'; const nextTabbable = focus.tabbable[ direction ]( event.target ); diff --git a/packages/block-library/src/post-featured-image/dimension-controls.js b/packages/block-library/src/post-featured-image/dimension-controls.js index 9a71a96b2db846..4bc343635f2f93 100644 --- a/packages/block-library/src/post-featured-image/dimension-controls.js +++ b/packages/block-library/src/post-featured-image/dimension-controls.js @@ -10,19 +10,7 @@ import { __experimentalUseCustomUnits as useCustomUnits, __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; -import { - useSettings, - privateApis as blockEditorPrivateApis, - store as blockEditorStore, -} from '@wordpress/block-editor'; -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { unlock } from '../lock-unlock'; - -const { ResolutionTool } = unlock( blockEditorPrivateApis ); +import { useSettings } from '@wordpress/block-editor'; const SCALE_OPTIONS = ( <> @@ -45,7 +33,6 @@ const SCALE_OPTIONS = ( ); const DEFAULT_SCALE = 'cover'; -const DEFAULT_SIZE = 'full'; const scaleHelp = { cover: __( @@ -61,9 +48,8 @@ const scaleHelp = { const DimensionControls = ( { clientId, - attributes: { aspectRatio, width, height, scale, sizeSlug }, + attributes: { aspectRatio, width, height, scale }, setAttributes, - media, } ) => { const [ availableUnits, defaultRatios, themeRatios, showDefaultRatios ] = useSettings( @@ -75,18 +61,6 @@ const DimensionControls = ( { const units = useCustomUnits( { availableUnits: availableUnits || [ 'px', '%', 'vw', 'em', 'rem' ], } ); - const imageSizes = useSelect( - ( select ) => select( blockEditorStore ).getSettings().imageSizes, - [] - ); - const imageSizeOptions = imageSizes - .filter( ( { slug } ) => { - return media?.media_details?.sizes?.[ slug ]?.source_url; - } ) - .map( ( { name, slug } ) => ( { - value: slug, - label: name, - } ) ); const onDimensionChange = ( dimension, nextValue ) => { const parsedValue = parseFloat( nextValue ); @@ -230,21 +204,6 @@ const DimensionControls = ( { ) } - { !! imageSizeOptions.length && ( - - setAttributes( { sizeSlug: nextSizeSlug } ) - } - isShownByDefault={ false } - resetAllFilter={ () => ( { - sizeSlug: DEFAULT_SIZE, - } ) } - /> - ) } ); }; diff --git a/packages/block-library/src/post-featured-image/edit.js b/packages/block-library/src/post-featured-image/edit.js index 6afe2c29e25045..9fd64b29908e42 100644 --- a/packages/block-library/src/post-featured-image/edit.js +++ b/packages/block-library/src/post-featured-image/edit.js @@ -27,6 +27,8 @@ import { __experimentalUseBorderProps as useBorderProps, __experimentalGetShadowClassesAndStyles as getShadowClassesAndStyles, useBlockEditingMode, + privateApis as blockEditorPrivateApis, + store as blockEditorStore, } from '@wordpress/block-editor'; import { useMemo, useEffect, useState } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; @@ -40,12 +42,37 @@ import DimensionControls from './dimension-controls'; import OverlayControls from './overlay-controls'; import Overlay from './overlay'; import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; +import { unlock } from '../lock-unlock'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; +const { ResolutionTool } = unlock( blockEditorPrivateApis ); +const DEFAULT_MEDIA_SIZE_SLUG = 'full'; + +function FeaturedImageResolutionTool( { image, value, onChange } ) { + const { imageSizes } = useSelect( ( select ) => { + const { getSettings } = select( blockEditorStore ); + return { + imageSizes: getSettings().imageSizes, + }; + }, [] ); + + if ( ! imageSizes?.length ) { + return null; + } + + const imageSizeOptions = imageSizes + .filter( + ( { slug } ) => image?.media_details?.sizes?.[ slug ]?.source_url + ) + .map( ( { name, slug } ) => ( { value: slug, label: name } ) ); -function getMediaSourceUrlBySizeSlug( media, slug ) { return ( - media?.media_details?.sizes?.[ slug ]?.source_url || media?.source_url + ); } @@ -125,7 +152,9 @@ export default function PostFeaturedImageEdit( { [ featuredImage, postTypeSlug, postId ] ); - const mediaUrl = getMediaSourceUrlBySizeSlug( media, sizeSlug ); + const mediaUrl = + media?.media_details?.sizes?.[ sizeSlug ]?.source_url || + media?.source_url; const blockProps = useBlockProps( { style: { width, height, aspectRatio }, @@ -291,6 +320,13 @@ export default function PostFeaturedImageEdit( { /> ) } + + setAttributes( { sizeSlug: nextSizeSlug } ) + } + /> diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js index c58990233f3615..35d0a95501a1b2 100644 --- a/packages/block-library/src/post-template/edit.js +++ b/packages/block-library/src/post-template/edit.js @@ -177,18 +177,27 @@ export default function PostTemplateEdit( { query.format = format; } - // If sticky is not set, it will return all posts in the results. - // If sticky is set to `only`, it will limit the results to sticky posts only. - // If it is anything else, it will exclude sticky posts from results. For the record the value stored is `exclude`. - if ( sticky ) { + /* + * Handle cases where sticky is set to `exclude` or `only`. + * Which works as a `post__in/post__not_in` query for sticky posts. + */ + if ( sticky && sticky !== 'ignore' ) { query.sticky = sticky === 'only'; } + + if ( sticky === 'ignore' ) { + // Remove any leftover sticky query parameter. + delete query.sticky; + query.ignore_sticky = true; + } + // If `inherit` is truthy, adjust conditionally the query to create a better preview. + let currentPostType = postType; if ( inherit ) { // Change the post-type if needed. if ( templateSlug?.startsWith( 'archive-' ) ) { query.postType = templateSlug.replace( 'archive-', '' ); - postType = query.postType; + currentPostType = query.postType; } else if ( templateCategory ) { query.categories = templateCategory[ 0 ]?.id; } else if ( templateTag ) { @@ -205,7 +214,7 @@ export default function PostTemplateEdit( { } // When we preview Query Loop blocks we should prefer the current // block's postType, which is passed through block context. - const usedPostType = previewPostType || postType; + const usedPostType = previewPostType || currentPostType; return { posts: getEntityRecords( 'postType', usedPostType, { ...query, diff --git a/packages/block-library/src/query/edit/inspector-controls/sticky-control.js b/packages/block-library/src/query/edit/inspector-controls/sticky-control.js index ee7ee31ba977a9..f478337998f388 100644 --- a/packages/block-library/src/query/edit/inspector-controls/sticky-control.js +++ b/packages/block-library/src/query/edit/inspector-controls/sticky-control.js @@ -6,6 +6,7 @@ import { __ } from '@wordpress/i18n'; const stickyOptions = [ { label: __( 'Include' ), value: '' }, + { label: __( 'Ignore' ), value: 'ignore' }, { label: __( 'Exclude' ), value: 'exclude' }, { label: __( 'Only' ), value: 'only' }, ]; diff --git a/packages/block-library/src/social-links/editor.scss b/packages/block-library/src/social-links/editor.scss index 54a4154659eb2d..6b1286c94757d8 100644 --- a/packages/block-library/src/social-links/editor.scss +++ b/packages/block-library/src/social-links/editor.scss @@ -100,6 +100,10 @@ .wp-block-social-links .block-list-appender { position: static; // display inline. + .block-editor-inserter { + font-size: inherit; + } + .block-editor-button-block-appender { height: 1.5em; width: 1.5em; diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 5f06bc110f2d9d..4ff01907284a4b 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,9 +2,10 @@ ## Unreleased -### Bug Fixes - `FontSizePicker`: Remove Custom option from dropdown to prevent unexpected context changes during keyboard navigation ([#69038](https://github.com/WordPress/gutenberg/pull/69038)). +- `ComboboxControl`: Add an `isLoading` prop to show a loading spinner ([#68990](https://github.com/WordPress/gutenberg/pull/68990)) + ## 29.3.0 (2025-01-29) ### Enhancements diff --git a/packages/components/src/combobox-control/README.md b/packages/components/src/combobox-control/README.md index 4089cf9c56e9b5..7a11c8b95fe2e9 100644 --- a/packages/components/src/combobox-control/README.md +++ b/packages/components/src/combobox-control/README.md @@ -39,6 +39,7 @@ function MyComboboxControl() { label="Font Size" value={ fontSize } onChange={ setFontSize } + isLoading={ isLoading } options={ filteredOptions } onFilterValueChange={ ( inputValue ) => setFilteredOptions( @@ -112,13 +113,20 @@ If the control is clicked, the dropdown will expand regardless of this prop. - Required: No - Default: `true` -### placeholder +#### placeholder If passed, the combobox input will show a placeholder string if no values are present. - Type: `string` - Required: No +#### isLoading + +Show a spinner (and hide the suggestions dropdown) while data about the matching suggestions (ie the `options` prop) is loading + +- Type: `Boolean` +- Required: No + #### __experimentalRenderItem Custom renderer invoked for each option in the suggestion list. The render prop receives as its argument an object containing, under the `item` key, the single option's data (directly from the array of data passed to the `options` prop). diff --git a/packages/components/src/combobox-control/index.tsx b/packages/components/src/combobox-control/index.tsx index 28510c8653d02e..9c6bd2a4d75dd2 100644 --- a/packages/components/src/combobox-control/index.tsx +++ b/packages/components/src/combobox-control/index.tsx @@ -35,6 +35,7 @@ import type { TokenInputProps } from '../form-token-field/types'; import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props'; import { withIgnoreIMEEvents } from '../utils/with-ignore-ime-events'; import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; +import Spinner from '../spinner'; const noop = () => {}; @@ -126,6 +127,7 @@ function ComboboxControl( props: ComboboxControlProps ) { help, allowReset = true, className, + isLoading = false, messages = { selected: __( 'Item selected.' ), }, @@ -362,6 +364,7 @@ function ComboboxControl( props: ComboboxControlProps ) { onChange={ onInputChange } /> + { isLoading && } { allowReset && (