diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml index 3698441458ce0c..a93c3ea032fd8a 100644 --- a/.github/workflows/publish-npm-packages.yml +++ b/.github/workflows/publish-npm-packages.yml @@ -53,6 +53,9 @@ jobs: with: path: publish ref: wp/${{ github.event.inputs.wp_version }} + # We need to ensure that Lerna can read the commit created during the previous npm publishing. + # Lerna assumes that all packages need publishing if it can't access the necessary information. + fetch-depth: 999 token: ${{ secrets.GUTENBERG_TOKEN }} show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index f2ac414842a1ff..577e1e80a0f799 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -74,6 +74,16 @@ jobs: - name: Setup Node.js and install dependencies uses: ./.github/setup-node + - name: Restore tests setup cache + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + with: + path: | + ~/.appium + key: ${{ runner.os }}-tests-setup-${{ hashFiles('package-lock.json') }} + + - name: Prepare tests setup + run: npm run native test:e2e:setup + - name: Gradle cache uses: gradle/gradle-build-action@b5126f31dbc19dd434c3269bf8c28c315e121da2 # v2.8.1 diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index fd353188718728..3dbbb4906c0af1 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -18,8 +18,8 @@ jobs: if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} strategy: matrix: - xcode: ['13.3.1'] - device: ['iPhone 13'] + xcode: ['14.2'] + device: ['iPhone 14'] native-test-name: [gutenberg-editor-rendering] steps: @@ -113,9 +113,25 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} >>>>>>> upstream/trunk + - name: Switch Xcode version to ${{ matrix.xcode }} + run: sudo xcode-select --switch /Applications/Xcode_${{ matrix.xcode }}.app + + - name: Launch simulator + run: (open -a Simulator && xcrun simctl boot '${{ matrix.device }}') & + - name: Setup Node.js and install dependencies uses: ./.github/setup-node + - name: Restore tests setup cache + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + with: + path: | + ~/.appium + key: ${{ runner.os }}-tests-setup-${{ hashFiles('package-lock.json') }} + + - name: Prepare tests setup + run: npm run native test:e2e:setup + - name: Prepare build cache key run: find package-lock.json packages/react-native-editor/ios packages/react-native-aztec/ios packages/react-native-bridge/ios -type f -print0 | sort -z | xargs -0 shasum | tee ios-checksums.txt @@ -140,18 +156,12 @@ jobs: - name: Bundle iOS run: npm run native test:e2e:bundle:ios - - name: Switch Xcode version to ${{ matrix.xcode }} - run: sudo xcode-select --switch /Applications/Xcode_${{ matrix.xcode }}.app - - name: Build (if needed) run: test -e packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app/GutenbergDemo || npm run native test:e2e:build-app:ios - name: Build Web Driver Agent (if needed) run: test -d packages/react-native-editor/ios/build/WDA || npm run native test:e2e:build-wda - - name: Launch simulator - run: open -a Simulator && xcrun simctl boot '${{ matrix.device }}' - - name: Run iOS Device Tests run: TEST_RN_PLATFORM=ios npm run native device-tests:local ${{ matrix.native-test-name }} diff --git a/.github/workflows/upload-release-to-plugin-repo.yml b/.github/workflows/upload-release-to-plugin-repo.yml index 2b5ca0e5a85935..11ba57b439b593 100644 --- a/.github/workflows/upload-release-to-plugin-repo.yml +++ b/.github/workflows/upload-release-to-plugin-repo.yml @@ -200,7 +200,8 @@ jobs: svn st | grep '^?' | awk '{print $2}' | xargs -r svn add svn st | grep '^!' | awk '{print $2}' | xargs -r svn rm svn commit -m "Committing version $VERSION" \ - --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" + --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" \ + --config-option=servers:global:http-timeout=300 - name: Create the SVN tag working-directory: ./trunk @@ -254,4 +255,5 @@ jobs: - name: Add the new version directory and commit changes to the SVN repository run: | svn import "$VERSION" "$PLUGIN_REPO_URL/tags/$VERSION" -m "Committing version $VERSION" \ - --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" + --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" \ + --config-option=servers:global:http-timeout=300 diff --git a/bin/plugin/commands/packages.js b/bin/plugin/commands/packages.js index fb83060b6e9240..c2f673d51dcc33 100644 --- a/bin/plugin/commands/packages.js +++ b/bin/plugin/commands/packages.js @@ -83,24 +83,14 @@ async function checkoutNpmReleaseBranch( { /* * Create the release branch. * - * Note that we are grabbing an arbitrary depth of commits - * during the fetch. When `lerna` attempts to determine if - * a package needs an update, it looks at `git` history, - * and if we have pruned that history it will pre-emptively - * publish when it doesn't need to. - * - * We could set a different arbitrary depth if this isn't - * long enough or if it's excessive. We could also try and - * find a way to more specifically fetch what we expect to - * change. For example, if we knew we'll be performing - * updates every two weeks, we might be conservative and - * use `--shallow-since=4.weeks.ago`. - * - * At the time of writing, a depth of 100 pulls in all - * `trunk` commits from within the past week. + * Note that we are grabbing an arbitrary depth of commits (999) during the fetch. + * When Lerna attempts to determine if a package needs an update, it looks at + * `git` history to find the commit created during the previous npm publishing. + * Lerna assumes that all packages need publishing if it can't access + * the necessary information. */ await SimpleGit( gitWorkingDirectoryPath ) - .fetch( 'origin', npmReleaseBranch, [ '--depth=100' ] ) + .fetch( 'origin', npmReleaseBranch, [ '--depth=999' ] ) .checkout( npmReleaseBranch ); log( '>> The local npm release branch ' + diff --git a/changelog.txt b/changelog.txt index 07dd4757450562..eb2cc01a796f71 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,661 @@ == Changelog == += 16.9.0 = + +## Changelog + +### Features + +#### Create Block +- Introduce the transformer property. ([55423](https://github.com/WordPress/gutenberg/pull/55423)) + +#### Data Views +- Enable users to sort by `date`. ([55388](https://github.com/WordPress/gutenberg/pull/55388)) + +#### Block Library +- Enable Block Renaming support for (almost) all blocks. ([54426](https://github.com/WordPress/gutenberg/pull/54426)) + + +### Enhancements + +#### Command API +- Add block-specific commands as contextual suggestions [#53539]. ([53974](https://github.com/WordPress/gutenberg/pull/53974)) + +#### Components +- Add `Tabs` (a composable `TabPanel` v2). ([53960](https://github.com/WordPress/gutenberg/pull/53960)) +- Add `type="button"` to vanilla `'; + style="background: #000" + > + + '; $body_content = preg_replace( '/]+>/', $button, $body_content ); @@ -290,12 +320,13 @@ function block_core_image_render_lightbox( $block_content, $block ) { data-wp-on--touchmove="actions.core.image.handleTouchMove" data-wp-on--touchend="actions.core.image.handleTouchEnd" data-wp-on--click="actions.core.image.hideLightbox" + tabindex="-1" > - + HTML; @@ -309,8 +340,6 @@ function block_core_image_render_lightbox( $block_content, $block ) { * @since 6.4.0 * * @global WP_Scripts $wp_scripts - * - * @return void */ function block_core_image_ensure_interactivity_dependency() { global $wp_scripts; @@ -326,8 +355,6 @@ function block_core_image_ensure_interactivity_dependency() { /** * Registers the `core/image` block on server. - * - * @return void */ function register_block_core_image() { register_block_type_from_metadata( diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss index 2ef602982e57b5..0fde1262fdec2d 100644 --- a/packages/block-library/src/image/style.scss +++ b/packages/block-library/src/image/style.scss @@ -157,14 +157,28 @@ display: flex; flex-direction: column; + img { + cursor: zoom-in; + } + + img:hover + button { + opacity: 1; + } + button { + opacity: 0; border: none; - background: none; + background: #000; cursor: zoom-in; - width: 100%; - height: 100%; + width: 24px; + height: 24px; position: absolute; z-index: 100; + top: 10px; + right: 10px; + text-align: center; + padding: 0; + border-radius: 10%; &:focus-visible { outline: 5px auto #212121; @@ -172,10 +186,19 @@ outline-offset: 5px; } + &:hover { + cursor: pointer; + opacity: 1; + } + + &:focus { + opacity: 1; + } + &:hover, &:focus, &:not(:hover):not(:active):not(.has-background) { - background: none; + background: #000; border: none; } } diff --git a/packages/block-library/src/image/view.js b/packages/block-library/src/image/view.js index 3eb47dcc7cab4b..331c0e79c731fc 100644 --- a/packages/block-library/src/image/view.js +++ b/packages/block-library/src/image/view.js @@ -103,9 +103,10 @@ store( context.core.image.lastFocusedElement = window.document.activeElement; context.core.image.scrollDelta = 0; + context.core.image.pointerType = event.pointerType; context.core.image.lightboxEnabled = true; - setStyles( context, event ); + setStyles( context, context.core.image.imageRef ); context.core.image.scrollTopReset = window.pageYOffset || @@ -148,12 +149,15 @@ store( 'scroll', scrollCallback ); + // If we don't delay before changing the focus, + // the focus ring will appear on Firefox before + // the image has finished animating, which looks broken. + context.core.image.lightboxTriggerRef.focus( { + preventScroll: true, + } ); }, 450 ); context.core.image.lightboxEnabled = false; - context.core.image.lastFocusedElement.focus( { - preventScroll: true, - } ); } }, handleKeydown: ( { context, actions, event } ) => { @@ -188,11 +192,12 @@ store( } } }, - handleLoad: ( { state, context, effects, ref } ) => { + // This is fired just by lazily loaded + // images on the page, not all images. + handleLoad: ( { context, effects, ref } ) => { context.core.image.imageLoaded = true; context.core.image.imageCurrentSrc = ref.currentSrc; effects.core.image.setButtonStyles( { - state, context, ref, } ); @@ -255,17 +260,18 @@ store( effects: { core: { image: { - setCurrentSrc: ( { context, ref } ) => { + initOriginImage: ( { context, ref } ) => { + context.core.image.imageRef = ref; + context.core.image.lightboxTriggerRef = + ref.parentElement.querySelector( + '.lightbox-trigger' + ); if ( ref.complete ) { context.core.image.imageLoaded = true; context.core.image.imageCurrentSrc = ref.currentSrc; } }, initLightbox: async ( { context, ref } ) => { - context.core.image.figureRef = - ref.querySelector( 'figure' ); - context.core.image.imageRef = - ref.querySelector( 'img' ); if ( context.core.image.lightboxEnabled ) { const focusableElements = ref.querySelectorAll( focusableSelectors ); @@ -276,10 +282,11 @@ store( focusableElements.length - 1 ]; - ref.querySelector( '.close-button' ).focus(); + // Move focus to the dialog when opening it. + ref.focus(); } }, - setButtonStyles: ( { state, context, ref } ) => { + setButtonStyles: ( { context, ref } ) => { const { naturalWidth, naturalHeight, @@ -288,54 +295,80 @@ store( } = ref; // If the image isn't loaded yet, we can't - // calculate how big the button should be. + // calculate where the button should be. if ( naturalWidth === 0 || naturalHeight === 0 ) { return; } - // Subscribe to the window dimensions so we can - // recalculate the styles if the window is resized. - if ( - ( state.core.image.windowWidth || - state.core.image.windowHeight ) && - context.core.image.scaleAttr === 'contain' - ) { - // In the case of an image with object-fit: contain, the - // size of the img element can be larger than the image itself, - // so we need to calculate the size of the button to match. + const figure = ref.parentElement; + const figureWidth = ref.parentElement.clientWidth; + + // We need special handling for the height because + // a caption will cause the figure to be taller than + // the image, which means we need to account for that + // when calculating the placement of the button in the + // top right corner of the image. + let figureHeight = ref.parentElement.clientHeight; + const caption = figure.querySelector( 'figcaption' ); + if ( caption ) { + const captionComputedStyle = + window.getComputedStyle( caption ); + figureHeight = + figureHeight - + caption.offsetHeight - + parseFloat( captionComputedStyle.marginTop ) - + parseFloat( captionComputedStyle.marginBottom ); + } + + const buttonOffsetTop = figureHeight - offsetHeight; + const buttonOffsetRight = figureWidth - offsetWidth; + // In the case of an image with object-fit: contain, the + // size of the element can be larger than the image itself, + // so we need to calculate where to place the button. + if ( context.core.image.scaleAttr === 'contain' ) { // Natural ratio of the image. const naturalRatio = naturalWidth / naturalHeight; // Offset ratio of the image. const offsetRatio = offsetWidth / offsetHeight; - if ( naturalRatio > offsetRatio ) { + if ( naturalRatio >= offsetRatio ) { // If it reaches the width first, keep - // the width and recalculate the height. - context.core.image.imageButtonWidth = - offsetWidth; - const buttonHeight = offsetWidth / naturalRatio; - context.core.image.imageButtonHeight = - buttonHeight; + // the width and compute the height. + const referenceHeight = + offsetWidth / naturalRatio; context.core.image.imageButtonTop = - ( offsetHeight - buttonHeight ) / 2; + ( offsetHeight - referenceHeight ) / 2 + + buttonOffsetTop + + 10; + context.core.image.imageButtonRight = + buttonOffsetRight + 10; } else { // If it reaches the height first, keep - // the height and recalculate the width. - context.core.image.imageButtonHeight = - offsetHeight; - const buttonWidth = offsetHeight * naturalRatio; - context.core.image.imageButtonWidth = - buttonWidth; - context.core.image.imageButtonLeft = - ( offsetWidth - buttonWidth ) / 2; + // the height and compute the width. + const referenceWidth = + offsetHeight * naturalRatio; + context.core.image.imageButtonTop = + buttonOffsetTop + 10; + context.core.image.imageButtonRight = + ( offsetWidth - referenceWidth ) / 2 + + buttonOffsetRight + + 10; } } else { - // In all other cases, we can trust that the size of - // the image is the right size for the button as well. - - context.core.image.imageButtonWidth = offsetWidth; - context.core.image.imageButtonHeight = offsetHeight; + context.core.image.imageButtonTop = + buttonOffsetTop + 10; + context.core.image.imageButtonRight = + buttonOffsetRight + 10; + } + }, + setStylesOnResize: ( { state, context, ref } ) => { + if ( + context.core.image.lightboxEnabled && + ( state.core.image.windowWidth || + state.core.image.windowHeight ) + ) { + setStyles( context, ref ); } }, }, @@ -362,7 +395,7 @@ store( * @param {Object} context - An Interactivity API context * @param {Object} event - A triggering event */ -function setStyles( context, event ) { +function setStyles( context, ref ) { // The reference img element lies adjacent // to the event target button in the DOM. let { @@ -370,9 +403,8 @@ function setStyles( context, event ) { naturalHeight, offsetWidth: originalWidth, offsetHeight: originalHeight, - } = event.target.previousElementSibling; - let { x: screenPosX, y: screenPosY } = - event.target.previousElementSibling.getBoundingClientRect(); + } = ref; + let { x: screenPosX, y: screenPosY } = ref.getBoundingClientRect(); // Natural ratio of the image clicked to open the lightbox. const naturalRatio = naturalWidth / naturalHeight; diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 586ecc59432730..0efe538b01f629 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -484,9 +484,10 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) { .join( ' ' ) } { createInterpolateElement( sprintf( - /* translators: 1: The static string "Read more", 2: The post title only visible to screen readers. */ - __( '… %1$s: %2$s' ), - __( 'Read more' ), + /* translators: 1: Hidden accessibility text: Post title */ + __( + '… Read more: %1$s' + ), titleTrimmed || __( '(no title)' ) ), { diff --git a/packages/block-library/src/latest-posts/index.php b/packages/block-library/src/latest-posts/index.php index d5f759c0c0e259..adc51d0c4fecb9 100644 --- a/packages/block-library/src/latest-posts/index.php +++ b/packages/block-library/src/latest-posts/index.php @@ -152,10 +152,9 @@ function render_block_core_latest_posts( $attributes ) { if ( $excerpt_length <= $block_core_latest_posts_excerpt_length ) { $trimmed_excerpt = substr( $trimmed_excerpt, 0, -11 ); $trimmed_excerpt .= sprintf( - /* translators: 1: A URL to a post, 2: The static string "Read more", 3: The post title only visible to screen readers. */ - __( '… %2$s: %3$s' ), + /* translators: 1: A URL to a post, 2: Hidden accessibility text: Post title */ + __( '… Read more: %2$s' ), esc_url( $post_link ), - __( 'Read more' ), esc_html( $title ) ); } diff --git a/packages/block-library/src/list-item/hooks/use-merge.js b/packages/block-library/src/list-item/hooks/use-merge.js index 6b456a2a742bdb..cda1f0c02d3a88 100644 --- a/packages/block-library/src/list-item/hooks/use-merge.js +++ b/packages/block-library/src/list-item/hooks/use-merge.js @@ -76,6 +76,24 @@ export default function useMerge( clientId, onMerge ) { } return ( forward ) => { + function mergeWithNested( clientIdA, clientIdB ) { + registry.batch( () => { + // When merging a sub list item with a higher next list item, we + // also need to move any nested list items. Check if there's a + // listed list, and append its nested list items to the current + // list. + const [ nestedListClientId ] = getBlockOrder( clientIdB ); + if ( nestedListClientId ) { + moveBlocksToPosition( + getBlockOrder( nestedListClientId ), + nestedListClientId, + getBlockRootClientId( clientIdA ) + ); + } + mergeBlocks( clientIdA, clientIdB ); + } ); + } + if ( forward ) { const nextBlockClientId = getNextId( clientId ); @@ -87,14 +105,7 @@ export default function useMerge( clientId, onMerge ) { if ( getParentListItemId( nextBlockClientId ) ) { outdentListItem( nextBlockClientId ); } else { - registry.batch( () => { - moveBlocksToPosition( - getBlockOrder( nextBlockClientId ), - nextBlockClientId, - getPreviousBlockClientId( nextBlockClientId ) - ); - mergeBlocks( clientId, nextBlockClientId ); - } ); + mergeWithNested( clientId, nextBlockClientId ); } } else { // Merging is only done from the top level. For lowel levels, the @@ -104,21 +115,7 @@ export default function useMerge( clientId, onMerge ) { outdentListItem( clientId ); } else if ( previousBlockClientId ) { const trailingId = getTrailingId( previousBlockClientId ); - registry.batch( () => { - // When merging a list item with a previous trailing list - // item, we also need to move any nested list items. First, - // check if there's a listed list. If there's a nested list, - // append its nested list items to the trailing list. - const [ nestedListClientId ] = getBlockOrder( clientId ); - if ( nestedListClientId ) { - moveBlocksToPosition( - getBlockOrder( nestedListClientId ), - nestedListClientId, - getBlockRootClientId( trailingId ) - ); - } - mergeBlocks( trailingId, clientId ); - } ); + mergeWithNested( trailingId, clientId ); } else { onMerge( forward ); } diff --git a/packages/block-library/src/lock-unlock.js b/packages/block-library/src/lock-unlock.js index 3fef0820721be1..3c18e76b798cda 100644 --- a/packages/block-library/src/lock-unlock.js +++ b/packages/block-library/src/lock-unlock.js @@ -5,6 +5,6 @@ import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/pri export const { lock, unlock } = __dangerousOptInToUnstableAPIsOnlyForCoreModules( - 'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.', + 'I know using unstable features means my theme or plugin will inevitably break in the next version of WordPress.', '@wordpress/block-library' ); diff --git a/packages/block-library/src/missing/edit.native.js b/packages/block-library/src/missing/edit.native.js index cf590dc0181c4f..a6164f590ca21d 100644 --- a/packages/block-library/src/missing/edit.native.js +++ b/packages/block-library/src/missing/edit.native.js @@ -11,21 +11,20 @@ import { /** * WordPress dependencies */ -import { - requestUnsupportedBlockFallback, - sendActionButtonPressedAction, - actionButtons, -} from '@wordpress/react-native-bridge'; -import { BottomSheet, Icon, TextControl } from '@wordpress/components'; +import { Icon } from '@wordpress/components'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { coreBlocks } from '@wordpress/block-library'; -import { normalizeIconObject } from '@wordpress/blocks'; +import { normalizeIconObject, rawHandler, serialize } from '@wordpress/blocks'; import { Component } from '@wordpress/element'; import { __, _x, sprintf } from '@wordpress/i18n'; import { help, plugins } from '@wordpress/icons'; import { withSelect, withDispatch } from '@wordpress/data'; import { applyFilters } from '@wordpress/hooks'; -import { store as blockEditorStore } from '@wordpress/block-editor'; +import { + UnsupportedBlockDetails, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -36,6 +35,8 @@ import styles from './style.scss'; const UBE_INCOMPATIBLE_BLOCKS = [ 'core/block' ]; const I18N_BLOCK_SCHEMA_TITLE = 'block title'; +const EMPTY_ARRAY = []; + export class UnsupportedBlockEdit extends Component { constructor( props ) { super( props ); @@ -121,122 +122,50 @@ export class UnsupportedBlockEdit extends Component { } renderSheet( blockTitle, blockName ) { - const { - getStylesFromColorScheme, - attributes, - clientId, - isUnsupportedBlockEditorSupported, - canEnableUnsupportedBlockEditor, - isEditableInUnsupportedBlockEditor, - } = this.props; - const infoTextStyle = getStylesFromColorScheme( - styles.infoText, - styles.infoTextDark - ); - const infoTitleStyle = getStylesFromColorScheme( - styles.infoTitle, - styles.infoTitleDark - ); - const infoDescriptionStyle = getStylesFromColorScheme( - styles.infoDescription, - styles.infoDescriptionDark - ); - const infoSheetIconStyle = getStylesFromColorScheme( - styles.infoSheetIcon, - styles.infoSheetIconDark - ); + const { block, clientId, createSuccessNotice, replaceBlocks } = + this.props; + const { showHelp } = this.state; /* translators: Missing block alert title. %s: The localized block name */ const titleFormat = __( "'%s' is not fully-supported" ); - const infoTitle = sprintf( titleFormat, blockTitle ); - const missingBlockDetail = applyFilters( + const title = sprintf( titleFormat, blockTitle ); + let description = applyFilters( 'native.missing_block_detail', __( 'We are working hard to add more blocks with each release.' ), blockName ); - const missingBlockActionButton = applyFilters( - 'native.missing_block_action_button', - __( 'Edit using web editor' ) - ); + let customActions = EMPTY_ARRAY; - const actionButtonStyle = getStylesFromColorScheme( - styles.actionButton, - styles.actionButtonDark - ); + // For Classic blocks, we offer the alternative to convert the content to blocks. + if ( blockName === 'core/freeform' ) { + description += + ' ' + + __( 'Alternatively, you can convert the content to blocks.' ); + /* translators: displayed right after the classic block is converted to blocks. %s: The localized classic block name */ + const successNotice = __( "'%s' block converted to blocks" ); + customActions = [ + { + label: __( 'Convert to blocks' ), + onPress: () => { + createSuccessNotice( + sprintf( successNotice, blockTitle ) + ); + replaceBlocks( block ); + }, + }, + ]; + } return ( - { - if ( this.state.sendFallbackMessage ) { - // On iOS, onModalHide is called when the controller is still part of the hierarchy. - // A small delay will ensure that the controller has already been removed. - this.timeout = setTimeout( () => { - // For the Classic block, the content is kept in the `content` attribute. - const content = - blockName === 'core/freeform' - ? attributes.content - : attributes.originalContent; - requestUnsupportedBlockFallback( - content, - clientId, - blockName, - blockTitle - ); - }, 100 ); - this.setState( { sendFallbackMessage: false } ); - } else if ( this.state.sendButtonPressMessage ) { - this.timeout = setTimeout( () => { - sendActionButtonPressedAction( - actionButtons.missingBlockAlertActionButton - ); - }, 100 ); - this.setState( { sendButtonPressMessage: false } ); - } - } } - > - - - - { infoTitle } - - { isEditableInUnsupportedBlockEditor && - missingBlockDetail && ( - - { missingBlockDetail } - - ) } - - { ( isUnsupportedBlockEditorSupported || - canEnableUnsupportedBlockEditor ) && - isEditableInUnsupportedBlockEditor && ( - <> - - - - ) } - + ); } @@ -300,8 +229,9 @@ export class UnsupportedBlockEdit extends Component { } export default compose( [ - withSelect( ( select, { attributes } ) => { - const { capabilities } = select( blockEditorStore ).getSettings(); + withSelect( ( select, { attributes, clientId } ) => { + const { getBlock, getSettings } = select( blockEditorStore ); + const { capabilities } = getSettings(); return { isUnsupportedBlockEditorSupported: capabilities?.unsupportedBlockEditor === true, @@ -309,14 +239,23 @@ export default compose( [ capabilities?.canEnableUnsupportedBlockEditor === true, isEditableInUnsupportedBlockEditor: ! UBE_INCOMPATIBLE_BLOCKS.includes( attributes.originalName ), + block: getBlock( clientId ), }; } ), withDispatch( ( dispatch, ownProps ) => { - const { selectBlock } = dispatch( blockEditorStore ); + const { selectBlock, replaceBlocks } = dispatch( blockEditorStore ); + const { createSuccessNotice } = dispatch( noticesStore ); return { selectBlock() { selectBlock( ownProps.clientId ); }, + replaceBlocks( block ) { + replaceBlocks( + ownProps.clientId, + rawHandler( { HTML: serialize( block ) } ) + ); + }, + createSuccessNotice, }; } ), withPreferredColorScheme, diff --git a/packages/block-library/src/missing/style.native.scss b/packages/block-library/src/missing/style.native.scss index 9a56f82f7e3f0d..5d83c67f78b818 100644 --- a/packages/block-library/src/missing/style.native.scss +++ b/packages/block-library/src/missing/style.native.scss @@ -1,13 +1,3 @@ -/** @format */ -.content { - padding-top: 8; - padding-bottom: 0; - padding-left: 24; - padding-right: 24; - align-items: center; - justify-content: space-evenly; -} - .helpIconContainer { position: absolute; top: 0; @@ -20,12 +10,6 @@ align-items: flex-end; } -.infoContainer { - flex-direction: column; - align-items: center; - justify-content: flex-end; -} - .infoIcon { size: 36; height: 36; @@ -38,49 +22,6 @@ color: $dark-tertiary; } -.infoSheetIcon { - size: 36; - height: 36; - padding-top: 8; - padding-bottom: 8; - color: $gray; -} - -.infoSheetIconDark { - color: $gray-20; -} - -.infoText { - text-align: center; - color: $gray-dark; -} - -.infoTextDark { - color: $white; -} - -.infoTitle { - padding-top: 8; - padding-bottom: 12; - font-size: 20; - font-weight: bold; - color: $gray-dark; -} - -.infoTitleDark { - color: $white; -} - -.infoDescription { - padding-bottom: 24; - font-size: 16; - color: $gray-darken-20; -} - -.infoDescriptionDark { - color: $gray-20; -} - .unsupportedBlock { height: 142; background-color: #e0e0e0; // $light-dim @@ -136,11 +77,3 @@ .unsupportedBlockSubtitleDark { color: $gray-20; } - -.actionButton { - color: $blue-50; -} - -.actionButtonDark { - color: $blue-30; -} diff --git a/packages/block-library/src/missing/test/edit-integration.native.js b/packages/block-library/src/missing/test/edit-integration.native.js index 4e1a4779572f2f..04b0bce688c3d2 100644 --- a/packages/block-library/src/missing/test/edit-integration.native.js +++ b/packages/block-library/src/missing/test/edit-integration.native.js @@ -1,81 +1,167 @@ /** * External dependencies */ -import { initializeEditor, fireEvent, within } from 'test/helpers'; +import { + fireEvent, + getBlock, + initializeEditor, + screen, + setupCoreBlocks, + withFakeTimers, + within, +} from 'test/helpers'; +import { Platform } from 'react-native'; /** * WordPress dependencies */ -import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +import { unregisterBlockType } from '@wordpress/blocks'; import { setLocaleData } from '@wordpress/i18n'; +import { requestUnsupportedBlockFallback } from '@wordpress/react-native-bridge'; + +// Override modal mock to prevent unmounting it when is not visible. +// This is required to be able to trigger onClose and onDismiss events when +// the modal is dismissed. +jest.mock( 'react-native-modal', () => { + const mockComponent = require( 'react-native/jest/mockComponent' ); + return mockComponent( 'react-native-modal' ); +} ); -/** - * Internal dependencies - */ -import { registerCoreBlocks } from '../..'; +const TABLE_BLOCK_HTML = ` +
12
34
+`; +const MODAL_DISMISS_EVENT = Platform.OS === 'ios' ? 'onDismiss' : 'onModalHide'; -beforeAll( () => { - // Mock translations. - setLocaleData( { - 'block title\u0004Table': [ 'Tabla' ], - "'%s' is not fully-supported": [ '«%s» no es totalmente compatible' ], - } ); +setupCoreBlocks(); - // Register all core blocks. - registerCoreBlocks(); +beforeAll( () => { + // For the purpose of this test suite we consider Reusable blocks/Patterns as unsupported. + // For this reason we unregister it to force it to be rendered as an unsupported block. + unregisterBlockType( 'core/block' ); } ); -afterAll( () => { - // Clean up translations. - setLocaleData( {} ); +describe( 'Unsupported block', () => { + describe( 'localized elements', () => { + beforeEach( () => { + // Mock translations. + setLocaleData( { + 'block title\u0004Table': [ 'Tabla' ], + "'%s' is not fully-supported": [ + '«%s» no es totalmente compatible', + ], + } ); + } ); + + afterEach( () => { + // Clean up translations. + setLocaleData( {} ); + } ); + + it( 'requests translated block title in block placeholder', async () => { + await initializeEditor( { + initialHtml: TABLE_BLOCK_HTML, + } ); + + const missingBlock = getBlock( screen, 'Unsupported' ); + + const translatedTableTitle = + within( missingBlock ).getByText( 'Tabla' ); + + expect( translatedTableTitle ).toBeDefined(); + } ); + + it( 'requests translated block title in bottom sheet', async () => { + await initializeEditor( { + initialHtml: TABLE_BLOCK_HTML, + } ); + + const missingBlock = getBlock( screen, 'Unsupported' ); + + fireEvent.press( missingBlock ); - // Clean up registered blocks. - getBlockTypes().forEach( ( block ) => { - unregisterBlockType( block.name ); + const [ helpButton ] = + await screen.findAllByLabelText( 'Help button' ); + + fireEvent.press( helpButton ); + + const bottomSheetTitle = await screen.findByText( + '«Tabla» no es totalmente compatible' + ); + + expect( bottomSheetTitle ).toBeDefined(); + } ); } ); -} ); -describe( 'Unsupported block', () => { - it( 'requests translated block title in block placeholder', async () => { - const initialHtml = ` -
12
34
- `; - const screen = await initializeEditor( { - initialHtml, + it( 'requests web editor when UBE is available', async () => { + await initializeEditor( { + initialHtml: TABLE_BLOCK_HTML, + capabilities: { + unsupportedBlockEditor: true, + canEnableUnsupportedBlockEditor: true, + }, } ); - const [ missingBlock ] = await screen.findAllByLabelText( - /Unsupported Block\. Row 1/ - ); + const missingBlock = getBlock( screen, 'Unsupported' ); + fireEvent.press( missingBlock ); + + // Tap the block to open the unsupported block details + fireEvent.press( within( missingBlock ).getByText( 'Unsupported' ) ); - const translatedTableTitle = - within( missingBlock ).getByText( 'Tabla' ); + const actionButton = screen.getByText( 'Edit using web editor' ); + expect( actionButton ).toBeVisible(); - expect( translatedTableTitle ).toBeDefined(); + // UBE is requested after the modal hides and running a timeout + await withFakeTimers( async () => { + fireEvent.press( actionButton ); + fireEvent( + screen.getByTestId( 'bottom-sheet' ), + MODAL_DISMISS_EVENT + ); + jest.runOnlyPendingTimers(); + } ); + expect( requestUnsupportedBlockFallback ).toHaveBeenCalled(); } ); - it( 'requests translated block title in bottom sheet', async () => { - const initialHtml = ` -
12
34
- `; - const screen = await initializeEditor( { - initialHtml, + it( 'does not show web editor option when UBE is not available', async () => { + await initializeEditor( { + initialHtml: TABLE_BLOCK_HTML, + capabilities: { + unsupportedBlockEditor: false, + canEnableUnsupportedBlockEditor: false, + }, } ); - const [ missingBlock ] = await screen.findAllByLabelText( - /Unsupported Block\. Row 1/ + const missingBlock = getBlock( screen, 'Unsupported' ); + fireEvent.press( missingBlock ); + + // Tap the block to open the unsupported block details + fireEvent.press( within( missingBlock ).getByText( 'Unsupported' ) ); + + const actionButton = await screen.queryByText( + 'Edit using web editor' ); + expect( actionButton ).toBeNull(); + } ); - fireEvent.press( missingBlock ); + it( 'does not show web editor option when block is incompatible with UBE', async () => { + await initializeEditor( { + // Reusable blocks/Patterns is a block type unsupported by UBE + initialHtml: '', + capabilities: { + unsupportedBlockEditor: true, + canEnableUnsupportedBlockEditor: true, + }, + } ); - const [ helpButton ] = await screen.findAllByLabelText( 'Help button' ); + const missingBlock = getBlock( screen, 'Unsupported' ); + fireEvent.press( missingBlock ); - fireEvent.press( helpButton ); + // Tap the block to open the unsupported block details + fireEvent.press( within( missingBlock ).getByText( 'Unsupported' ) ); - const bottomSheetTitle = await screen.findByText( - '«Tabla» no es totalmente compatible' + const actionButton = await screen.queryByText( + 'Edit using web editor' ); - - expect( bottomSheetTitle ).toBeDefined(); + expect( actionButton ).toBeNull(); } ); } ); diff --git a/packages/block-library/src/missing/test/edit.native.js b/packages/block-library/src/missing/test/edit.native.js index 5905a98cd88906..47d0da572b7c88 100644 --- a/packages/block-library/src/missing/test/edit.native.js +++ b/packages/block-library/src/missing/test/edit.native.js @@ -63,47 +63,6 @@ describe( 'Missing block', () => { "' is not fully-supported" ); } ); - - describe( 'Unsupported block editor (UBE)', () => { - beforeEach( () => { - // By default we set the web editor as available. - storeConfig.selectors.getSettings.mockReturnValue( { - capabilities: { unsupportedBlockEditor: true }, - } ); - } ); - - it( 'renders edit action if UBE is available', () => { - const testInstance = getTestComponentWithContent(); - const bottomSheet = - testInstance.UNSAFE_getByType( BottomSheet ); - const bottomSheetCells = bottomSheet.props.children[ 1 ]; - expect( bottomSheetCells ).toBeTruthy(); - expect( bottomSheetCells.props.children.length ).toBe( 2 ); - expect( bottomSheetCells.props.children[ 0 ].props.label ).toBe( - 'Edit using web editor' - ); - } ); - - it( 'does not render edit action if UBE is not available', () => { - storeConfig.selectors.getSettings.mockReturnValue( { - capabilities: { unsupportedBlockEditor: false }, - } ); - - const testInstance = getTestComponentWithContent(); - const bottomSheet = - testInstance.UNSAFE_getByType( BottomSheet ); - expect( bottomSheet.props.children[ 1 ] ).toBeFalsy(); - } ); - - it( 'does not render edit action if the block is incompatible with UBE', () => { - const testInstance = getTestComponentWithContent( { - originalName: 'core/block', - } ); - const bottomSheet = - testInstance.UNSAFE_getByType( BottomSheet ); - expect( bottomSheet.props.children[ 1 ] ).toBeFalsy(); - } ); - } ); } ); it( 'renders admin plugins icon', () => { diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index cb5ca4fec1b90f..9ec919ae38d1fa 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -133,7 +133,8 @@ } } }, - "interactivity": true + "interactivity": true, + "renaming": false }, "viewScript": "file:./view.min.js", "editorStyle": "wp-block-navigation-editor", diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 7c29f18d4940d4..f12d83e2fe2eae 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -884,7 +884,6 @@ function Navigation( { set_attribute( 'data-wp-effect', 'effects.core.navigation.initMenu' ); $w->set_attribute( 'data-wp-on--focusout', 'actions.core.navigation.handleMenuFocusout' ); $w->set_attribute( 'data-wp-on--keydown', 'actions.core.navigation.handleMenuKeydown' ); + + // This is a fix for Safari. Without it, Safari doesn't change the active + // element when the user clicks on a button. It can be removed once we add + // an overlay to capture the clicks, instead of relying on the focusout + // event. + $w->set_attribute( 'tabindex', '-1' ); + if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) { $w->set_attribute( 'data-wp-on--mouseenter', 'actions.core.navigation.openMenuOnHover' ); $w->set_attribute( 'data-wp-on--mouseleave', 'actions.core.navigation.closeMenuOnHover' ); @@ -696,9 +703,22 @@ function render_block_core_navigation( $attributes, $content, $block ) { $responsive_dialog_directives = ''; $close_button_directives = ''; if ( $should_load_view_script ) { + $nav_element_context = wp_json_encode( + array( + 'core' => array( + 'navigation' => array( + 'overlayOpenedBy' => array(), + 'type' => 'overlay', + 'roleAttribute' => '', + 'ariaLabel' => __( 'Menu' ), + ), + ), + ), + JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP + ); $nav_element_directives = ' data-wp-interactive - data-wp-context=\'{ "core": { "navigation": { "overlayOpenedBy": {}, "type": "overlay", "roleAttribute": "" } } }\' + data-wp-context=\'' . $nav_element_context . '\' '; $open_button_directives = ' data-wp-on--click="actions.core.navigation.openMenuOnClick" @@ -714,6 +734,7 @@ function render_block_core_navigation( $attributes, $content, $block ) { '; $responsive_dialog_directives = ' data-wp-bind--aria-modal="selectors.core.navigation.ariaModal" + data-wp-bind--aria-label="selectors.core.navigation.ariaLabel" data-wp-bind--role="selectors.core.navigation.roleAttribute" data-wp-effect="effects.core.navigation.focusFirstElement" '; @@ -723,11 +744,11 @@ function render_block_core_navigation( $attributes, $content, $block ) { } $responsive_container_markup = sprintf( - ' -
+ ' +
-
- +
+
%2$s
@@ -741,7 +762,6 @@ function render_block_core_navigation( $attributes, $content, $block ) { esc_attr( implode( ' ', $responsive_container_classes ) ), esc_attr( implode( ' ', $open_button_classes ) ), esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) ), - __( 'Menu' ), $toggle_button_content, $toggle_close_button_content, $open_button_directives, diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index eb8ef7683e1988..0b70ebb656cfa8 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -317,7 +317,9 @@ button.wp-block-navigation-item__content { // When set to open on click, a button element is used. // We pad it to include the arrow icon in the clickable area. // The padding can be blanket for click, since you can't set click and hide the icon. +// This is only applied to the submenu in the page list block. .wp-block-navigation-item.open-on-click .wp-block-navigation-submenu__toggle { + padding-left: 0; // Remove the browser default padding. padding-right: 0.6em + 0.25em; // Same size as icon plus margin. + .wp-block-navigation__submenu-icon { @@ -325,7 +327,10 @@ button.wp-block-navigation-item__content { pointer-events: none; // Make the icon inert to allow click on the button. } } - +// Remove the browser default padding on the button element used in the navigation link submenu. +.wp-block-navigation-item.open-on-click button.wp-block-navigation-item__content:not(.wp-block-navigation-submenu__toggle) { + padding: 0; +} /** * Margins diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index 6a8e4979983b8a..bad36f6240134f 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -13,10 +13,14 @@ const focusableSelectors = [ '[tabindex]:not([tabindex^="-"])', ]; +// This is a fix for Safari in iOS/iPadOS. Without it, Safari doesn't focus out +// when the user taps in the body. It can be removed once we add an overlay to +// capture the clicks, instead of relying on the focusout event. +document.addEventListener( 'click', () => {} ); + const openMenu = ( store, menuOpenedOn ) => { - const { context, ref, selectors } = store; + const { context, selectors } = store; selectors.core.navigation.menuOpenedBy( store )[ menuOpenedOn ] = true; - context.core.navigation.previousFocus = ref; if ( context.core.navigation.type === 'overlay' ) { // Add a `has-modal-open` class to the root. document.documentElement.classList.add( 'has-modal-open' ); @@ -33,7 +37,7 @@ const closeMenu = ( store, menuClosedOn ) => { window.document.activeElement ) ) { - context.core.navigation.previousFocus.focus(); + context.core.navigation.previousFocus?.focus(); } context.core.navigation.modal = null; context.core.navigation.previousFocus = null; @@ -87,6 +91,13 @@ wpStore( { ? 'true' : null; }, + ariaLabel: ( store ) => { + const { context, selectors } = store; + return context.core.navigation.type === 'overlay' && + selectors.core.navigation.isMenuOpen( store ) + ? context.core.navigation.ariaLabel + : null; + }, isMenuOpen: ( { context } ) => // The menu is opened if either `click`, `hover` or `focus` is true. Object.values( @@ -123,6 +134,8 @@ wpStore( { closeMenu( store, 'hover' ); }, openMenuOnClick( store ) { + const { context, ref } = store; + context.core.navigation.previousFocus = ref; openMenu( store, 'click' ); }, closeMenuOnClick( store ) { @@ -133,13 +146,16 @@ wpStore( { openMenu( store, 'focus' ); }, toggleMenuOnClick: ( store ) => { - const { selectors } = store; + const { selectors, context, ref } = store; + // Safari won't send focus to the clicked element, so we need to manually place it: https://bugs.webkit.org/show_bug.cgi?id=22261 + if ( window.document.activeElement !== ref ) ref.focus(); const menuOpenedBy = selectors.core.navigation.menuOpenedBy( store ); if ( menuOpenedBy.click || menuOpenedBy.focus ) { closeMenu( store, 'click' ); closeMenu( store, 'focus' ); } else { + context.core.navigation.previousFocus = ref; openMenu( store, 'click' ); } }, @@ -187,11 +203,14 @@ wpStore( { // event.relatedTarget === The element receiving focus (if any) // When focusout is outsite the document, // `window.document.activeElement` doesn't change. + + // The event.relatedTarget is null when something outside the navigation menu is clicked. This is only necessary for Safari. if ( - ! context.core.navigation.modal?.contains( + event.relatedTarget === null || + ( ! context.core.navigation.modal?.contains( event.relatedTarget ) && - event.target !== window.document.activeElement + event.target !== window.document.activeElement ) ) { closeMenu( store, 'click' ); closeMenu( store, 'focus' ); diff --git a/packages/block-library/src/page-list-item/edit.js b/packages/block-library/src/page-list-item/edit.js index b99e602c590933..dda1ab155816f5 100644 --- a/packages/block-library/src/page-list-item/edit.js +++ b/packages/block-library/src/page-list-item/edit.js @@ -64,6 +64,7 @@ export default function PageListItemEdit( { context, attributes } ) { { hasChildren && context.openSubmenusOnClick ? ( <> diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index 21f692bd25b263..37a9c2ab9b10a9 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -18,7 +18,7 @@ import { InspectorControls, RichText, useBlockProps, - useSetting, + useSettings, } from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; import { formatLtr } from '@wordpress/icons'; @@ -58,7 +58,7 @@ function ParagraphBlock( { clientId, } ) { const { align, content, direction, dropCap, placeholder } = attributes; - const isDropCapFeatureEnabled = useSetting( 'typography.dropCap' ); + const [ isDropCapFeatureEnabled ] = useSettings( 'typography.dropCap' ); const blockProps = useBlockProps( { ref: useOnEnter( { clientId, content } ), className: classnames( { diff --git a/packages/block-library/src/paragraph/index.js b/packages/block-library/src/paragraph/index.js index bceff881367074..715fb35ec05ab1 100644 --- a/packages/block-library/src/paragraph/index.js +++ b/packages/block-library/src/paragraph/index.js @@ -28,7 +28,17 @@ export const settings = { }, }, __experimentalLabel( attributes, { context } ) { + const customName = attributes?.metadata?.name; + + if ( context === 'list-view' && customName ) { + return customName; + } + if ( context === 'accessibility' ) { + if ( customName ) { + return customName; + } + const { content } = attributes; return ! content || content.length === 0 ? __( 'Empty' ) : content; } diff --git a/packages/block-library/src/pattern/block.json b/packages/block-library/src/pattern/block.json index e9a85a9b2f84f1..da02f7b72747e4 100644 --- a/packages/block-library/src/pattern/block.json +++ b/packages/block-library/src/pattern/block.json @@ -7,7 +7,8 @@ "description": "Show a block pattern.", "supports": { "html": false, - "inserter": false + "inserter": false, + "renaming": false }, "textdomain": "default", "attributes": { diff --git a/packages/block-library/src/pattern/index.php b/packages/block-library/src/pattern/index.php index fc4652a7c22e89..f05bb333bd186d 100644 --- a/packages/block-library/src/pattern/index.php +++ b/packages/block-library/src/pattern/index.php @@ -7,8 +7,6 @@ /** * Registers the `core/pattern` block on the server. - * - * @return void */ function register_block_core_pattern() { register_block_type_from_metadata( @@ -46,7 +44,6 @@ function render_block_core_pattern( $attributes ) { // Backward compatibility for handling Block Hooks and injecting the theme attribute in the Gutenberg plugin. // This can be removed when the minimum supported WordPress is >= 6.4. if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN && ! function_exists( 'traverse_and_serialize_blocks' ) ) { - $content = _inject_theme_attribute_in_block_template_content( $content ); $blocks = parse_blocks( $content ); $content = gutenberg_serialize_blocks( $blocks ); } 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 5e1204922880c6..03a2d2849dae36 100644 --- a/packages/block-library/src/post-featured-image/dimension-controls.js +++ b/packages/block-library/src/post-featured-image/dimension-controls.js @@ -10,7 +10,7 @@ import { __experimentalUseCustomUnits as useCustomUnits, __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; -import { InspectorControls, useSetting } from '@wordpress/block-editor'; +import { InspectorControls, useSettings } from '@wordpress/block-editor'; const SCALE_OPTIONS = ( <> @@ -53,9 +53,9 @@ const DimensionControls = ( { setAttributes, imageSizeOptions = [], } ) => { - const defaultUnits = [ 'px', '%', 'vw', 'em', 'rem' ]; + const [ availableUnits ] = useSettings( 'spacing.units' ); const units = useCustomUnits( { - availableUnits: useSetting( 'spacing.units' ) || defaultUnits, + availableUnits: availableUnits || [ 'px', '%', 'vw', 'em', 'rem' ], } ); const onDimensionChange = ( dimension, nextValue ) => { const parsedValue = parseFloat( nextValue ); diff --git a/packages/block-library/src/post-navigation-link/index.php b/packages/block-library/src/post-navigation-link/index.php index fe5cc8394d6466..add112c388c6eb 100644 --- a/packages/block-library/src/post-navigation-link/index.php +++ b/packages/block-library/src/post-navigation-link/index.php @@ -38,6 +38,7 @@ function render_block_core_post_navigation_link( $attributes, $content ) { $link = 'next' === $navigation_type ? _x( 'Next', 'label for next post link' ) : _x( 'Previous', 'label for previous post link' ); $label = ''; + // Only use hardcoded values here, otherwise we need to add escaping where these values are used. $arrow_map = array( 'none' => '', 'arrow' => array( @@ -88,7 +89,7 @@ function render_block_core_post_navigation_link( $attributes, $content ) { } // Display arrows. - if ( isset( $attributes['arrow'] ) && ! empty( $attributes['arrow'] ) && 'none' !== $attributes['arrow'] ) { + if ( isset( $attributes['arrow'] ) && 'none' !== $attributes['arrow'] && isset( $arrow_map[ $attributes['arrow'] ] ) ) { $arrow = $arrow_map[ $attributes['arrow'] ][ $navigation_type ]; if ( 'next' === $navigation_type ) { diff --git a/packages/block-library/src/query/edit/enhanced-pagination-modal.js b/packages/block-library/src/query/edit/enhanced-pagination-modal.js index 04423116ac92e3..844a86447806c3 100644 --- a/packages/block-library/src/query/edit/enhanced-pagination-modal.js +++ b/packages/block-library/src/query/edit/enhanced-pagination-modal.js @@ -12,10 +12,10 @@ import { useState, useEffect } from '@wordpress/element'; /** * Internal dependencies */ -import { useContainsThirdPartyBlocks } from '../utils'; +import { useUnsupportedBlockList } from '../utils'; const disableEnhancedPaginationDescription = __( - 'Plugin blocks are not supported yet. For the enhanced pagination to work, remove the plugin block, then re-enable "Enhanced pagination" in the Query Block settings.' + 'You have added unsupported blocks. For the enhanced pagination to work, remove them, then re-enable "Enhanced pagination" in the Query Block settings.' ); const modalDescriptionId = @@ -28,11 +28,11 @@ export default function EnhancedPaginationModal( { } ) { const [ isOpen, setOpen ] = useState( false ); - const containsThirdPartyBlocks = useContainsThirdPartyBlocks( clientId ); + const unsupported = useUnsupportedBlockList( clientId ); useEffect( () => { - setOpen( containsThirdPartyBlocks && enhancedPagination ); - }, [ containsThirdPartyBlocks, enhancedPagination, setOpen ] ); + setOpen( !! unsupported.length && enhancedPagination ); + }, [ unsupported.length, enhancedPagination, setOpen ] ); return ( isOpen && ( @@ -42,6 +42,8 @@ export default function EnhancedPaginationModal( { aria={ { describedby: modalDescriptionId, } } + role="alertdialog" + focusOnMount="firstElement" isDismissible={ false } shouldCloseOnEsc={ false } shouldCloseOnClickOutside={ false } diff --git a/packages/block-library/src/query/edit/inspector-controls/enhanced-pagination-control.js b/packages/block-library/src/query/edit/inspector-controls/enhanced-pagination-control.js index 042c9f1e75930c..64b4f5d96eb38b 100644 --- a/packages/block-library/src/query/edit/inspector-controls/enhanced-pagination-control.js +++ b/packages/block-library/src/query/edit/inspector-controls/enhanced-pagination-control.js @@ -3,22 +3,19 @@ */ import { ToggleControl, Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { BlockTitle } from '@wordpress/block-editor'; /** * Internal dependencies */ -import { useContainsThirdPartyBlocks } from '../../utils'; +import { useUnsupportedBlockList } from '../../utils'; export default function EnhancedPaginationControl( { enhancedPagination, setAttributes, clientId, } ) { - const enhancedPaginationNotice = __( - "Enhanced pagination doesn't support plugin blocks yet. If you want to enable it, you have to remove all plugin blocks from the Query Loop." - ); - - const containsThirdPartyBlocks = useContainsThirdPartyBlocks( clientId ); + const unsupported = useUnsupportedBlockList( clientId ); return ( <> @@ -28,20 +25,32 @@ export default function EnhancedPaginationControl( { 'Browsing between pages won’t require a full page reload.' ) } checked={ !! enhancedPagination } - disabled={ containsThirdPartyBlocks } + disabled={ unsupported.length } onChange={ ( value ) => { setAttributes( { enhancedPagination: !! value, } ); } } /> - { containsThirdPartyBlocks && ( + { !! unsupported.length && ( - { enhancedPaginationNotice } + { __( + "Enhanced pagination doesn't support the following blocks:" + ) } +
    + { unsupported.map( ( id ) => ( +
  • + +
  • + ) ) } +
+ { __( + 'If you want to enable it, you have to remove all unsupported blocks first.' + ) }
) } diff --git a/packages/block-library/src/query/index.php b/packages/block-library/src/query/index.php index c35bf2ba00af21..5e89f21fc3028f 100644 --- a/packages/block-library/src/query/index.php +++ b/packages/block-library/src/query/index.php @@ -34,7 +34,8 @@ function render_block_core_query( $attributes, $content, $block ) { 'loadedText' => __( 'Page Loaded.' ), ), ), - ) + ), + JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP ) ); $content = $p->get_updated_html(); @@ -47,7 +48,7 @@ function render_block_core_query( $attributes, $content, $block ) { $content = substr_replace( $content, '
diff --git a/packages/block-library/src/query/style.scss b/packages/block-library/src/query/style.scss index 25003dcca5431b..4e9f4741beaed4 100644 --- a/packages/block-library/src/query/style.scss +++ b/packages/block-library/src/query/style.scss @@ -50,14 +50,3 @@ opacity: 0; } } - -.wp-block-query__enhanced-pagination-navigation-announce { - position: absolute; - clip: rect(0, 0, 0, 0); - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - border: 0; -} diff --git a/packages/block-library/src/query/utils.js b/packages/block-library/src/query/utils.js index dd68ee38af9166..322c8f4c1453fb 100644 --- a/packages/block-library/src/query/utils.js +++ b/packages/block-library/src/query/utils.js @@ -346,21 +346,28 @@ export const usePatterns = ( clientId, name ) => { }; /** - * Hook that returns whether the Query Loop with the given `clientId` contains - * any third-party block. + * Hook that returns a list of unsupported blocks inside the Query Loop with the + * given `clientId`. * * @param {string} clientId The block's client ID. - * @return {boolean} True if it contains third-party blocks. + * @return {string[]} List of block titles. */ -export const useContainsThirdPartyBlocks = ( clientId ) => { +export const useUnsupportedBlockList = ( clientId ) => { return useSelect( ( select ) => { const { getClientIdsOfDescendants, getBlockName } = select( blockEditorStore ); - return getClientIdsOfDescendants( clientId ).some( - ( descendantClientId ) => - ! getBlockName( descendantClientId ).startsWith( 'core/' ) + return getClientIdsOfDescendants( clientId ).filter( + ( descendantClientId ) => { + const blockName = getBlockName( descendantClientId ); + return ( + ! blockName.startsWith( 'core/' ) || + blockName === 'core/post-content' || + blockName === 'core/template-part' || + blockName === 'core/block' + ); + } ); }, [ clientId ] diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index 616478d8013f72..52f89d344cdf02 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -16,7 +16,7 @@ import { getTypographyClassesAndStyles as useTypographyProps, store as blockEditorStore, __experimentalGetElementClassName, - useSetting, + useSettings, } from '@wordpress/block-editor'; import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect, useRef } from '@wordpress/element'; @@ -125,8 +125,10 @@ export default function SearchEdit( { } const colorProps = useColorProps( attributes ); - const fluidTypographySettings = useSetting( 'typography.fluid' ); - const layout = useSetting( 'layout' ); + const [ fluidTypographySettings, layout ] = useSettings( + 'typography.fluid', + 'layout' + ); const typographyProps = useTypographyProps( attributes, { typography: { fluid: fluidTypographySettings, diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index ed3d1cf4b847a7..f00ecfe6abe1cc 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -281,8 +281,6 @@ function classnames_for_block_core_search( $attributes ) { * @param array $wrapper_styles Current collection of wrapper styles. * @param array $button_styles Current collection of button styles. * @param array $input_styles Current collection of input styles. - * - * @return void */ function apply_block_core_search_border_style( $attributes, $property, $side, &$wrapper_styles, &$button_styles, &$input_styles ) { $is_button_inside = isset( $attributes['buttonPosition'] ) && 'button-inside' === $attributes['buttonPosition']; @@ -327,8 +325,6 @@ function apply_block_core_search_border_style( $attributes, $property, $side, &$ * @param array $wrapper_styles Current collection of wrapper styles. * @param array $button_styles Current collection of button styles. * @param array $input_styles Current collection of input styles. - * - * @return void */ function apply_block_core_search_border_styles( $attributes, $property, &$wrapper_styles, &$button_styles, &$input_styles ) { apply_block_core_search_border_style( $attributes, $property, null, $wrapper_styles, $button_styles, $input_styles ); diff --git a/packages/block-library/src/social-link/edit.native.js b/packages/block-library/src/social-link/edit.native.js index 856c6aa704a4e6..3ea794a9b8c20b 100644 --- a/packages/block-library/src/social-link/edit.native.js +++ b/packages/block-library/src/social-link/edit.native.js @@ -16,7 +16,7 @@ import { ToolbarButton, LinkSettingsNavigation, } from '@wordpress/components'; -import { compose, usePreferredColorSchemeStyle } from '@wordpress/compose'; +import { compose } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; import { link, Icon } from '@wordpress/icons'; import { withSelect } from '@wordpress/data'; @@ -30,10 +30,6 @@ const DEFAULT_ACTIVE_ICON_STYLES = { backgroundColor: '#f0f0f0', color: '#444', }; -const DEFAULT_INACTIVE_ICON_STYLES = { - backgroundColor: '#0000003f', - color: '#fff', -}; const ANIMATION_DELAY = 300; const ANIMATION_DURATION = 400; @@ -66,12 +62,6 @@ const SocialLinkEdit = ( { styles[ `wp-social-link-${ service }` ] || styles[ `wp-social-link` ] || DEFAULT_ACTIVE_ICON_STYLES; - const inactiveIcon = - usePreferredColorSchemeStyle( - styles.inactiveIcon, - styles.inactiveIconDark - ) || DEFAULT_INACTIVE_ICON_STYLES; - const animatedValue = useRef( new Animated.Value( 0 ) ).current; const IconComponent = getIconBySite( service )(); @@ -94,23 +84,13 @@ const SocialLinkEdit = ( { }, [ url ] ); const interpolationColors = { - backgroundColor: animatedValue.interpolate( { - inputRange: [ 0, 1 ], - outputRange: [ - inactiveIcon.backgroundColor, - activeIcon.backgroundColor, - ], - } ), - color: animatedValue.interpolate( { + opacity: animatedValue.interpolate( { inputRange: [ 0, 1 ], - outputRange: [ inactiveIcon.color, activeIcon.color ], + outputRange: [ 0.3, 1 ], } ), - stroke: '', }; - const { backgroundColor, color, stroke } = hasUrl - ? activeIcon - : interpolationColors; + const { opacity } = hasUrl ? activeIcon : interpolationColors; function animateColors() { Animated.sequence( [ @@ -200,12 +180,18 @@ const SocialLinkEdit = ( { accessibilityHint={ accessibilityHint } > diff --git a/packages/block-library/src/social-link/editor.native.scss b/packages/block-library/src/social-link/editor.native.scss index 91b69de77de64e..fbbc8eb09e6a8c 100644 --- a/packages/block-library/src/social-link/editor.native.scss +++ b/packages/block-library/src/social-link/editor.native.scss @@ -13,15 +13,6 @@ justify-content: center; } -.inactiveIcon { - background-color: $light-quaternary; - color: $white; -} - -.inactiveIconDark { - background-color: $dark-quaternary; -} - .container { margin: $block-selected-margin; } diff --git a/packages/block-library/src/spacer/controls.js b/packages/block-library/src/spacer/controls.js index d999550f16f331..91a1e79be173eb 100644 --- a/packages/block-library/src/spacer/controls.js +++ b/packages/block-library/src/spacer/controls.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { InspectorControls, - useSetting, + useSettings, __experimentalSpacingSizesControl as SpacingSizesControl, isValueSpacingPreset, } from '@wordpress/block-editor'; @@ -25,22 +25,19 @@ import { MIN_SPACER_SIZE } from './constants'; function DimensionInput( { label, onChange, isResizing, value = '' } ) { const inputId = useInstanceId( UnitControl, 'block-spacer-height-input' ); - const spacingSizes = useSetting( 'spacing.spacingSizes' ); + const [ spacingSizes, spacingUnits ] = useSettings( + 'spacing.spacingSizes', + 'spacing.units' + ); // In most contexts the spacer size cannot meaningfully be set to a // percentage, since this is relative to the parent container. This // unit is disabled from the UI. - const availableUnitSettings = ( - useSetting( 'spacing.units' ) || undefined - )?.filter( ( availableUnit ) => availableUnit !== '%' ); + const availableUnits = spacingUnits + ? spacingUnits.filter( ( unit ) => unit !== '%' ) + : [ 'px', 'em', 'rem', 'vw', 'vh' ]; const units = useCustomUnits( { - availableUnits: availableUnitSettings || [ - 'px', - 'em', - 'rem', - 'vw', - 'vh', - ], + availableUnits, defaultValues: { px: 100, em: 10, rem: 10, vw: 10, vh: 25 }, } ); diff --git a/packages/block-library/src/spacer/controls.native.js b/packages/block-library/src/spacer/controls.native.js index 644359b1072751..6aacfae19fa82a 100644 --- a/packages/block-library/src/spacer/controls.native.js +++ b/packages/block-library/src/spacer/controls.native.js @@ -8,7 +8,7 @@ import { __experimentalUseCustomUnits as useCustomUnits, } from '@wordpress/components'; import { useCallback } from '@wordpress/element'; -import { useSetting } from '@wordpress/block-editor'; +import { useSettings } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; /** @@ -59,14 +59,9 @@ function Controls( { [ height, width ] ); + const [ availableUnits ] = useSettings( 'spacing.units' ); const units = useCustomUnits( { - availableUnits: useSetting( 'spacing.units' ) || [ - 'px', - 'em', - 'rem', - 'vw', - 'vh', - ], + availableUnits: availableUnits || [ 'px', 'em', 'rem', 'vw', 'vh' ], defaultValues: DEFAULT_VALUES, } ); diff --git a/packages/block-library/src/spacer/edit.js b/packages/block-library/src/spacer/edit.js index 1786c435ebbf23..11133732042d3f 100644 --- a/packages/block-library/src/spacer/edit.js +++ b/packages/block-library/src/spacer/edit.js @@ -8,7 +8,7 @@ import classnames from 'classnames'; */ import { useBlockProps, - useSetting, + useSettings, getCustomValueFromPreset, getSpacingPresetCssVar, store as blockEditorStore, @@ -107,7 +107,7 @@ const SpacerEdit = ( { const { layout = {} } = blockStyle; const { selfStretch, flexSize } = layout; - const spacingSizes = useSetting( 'spacing.spacingSizes' ); + const [ spacingSizes ] = useSettings( 'spacing.spacingSizes' ); const [ isResizing, setIsResizing ] = useState( false ); const [ temporaryHeight, setTemporaryHeight ] = useState( null ); diff --git a/packages/block-library/src/spacer/edit.native.js b/packages/block-library/src/spacer/edit.native.js index ca5cfa409939bf..614624570a6d95 100644 --- a/packages/block-library/src/spacer/edit.native.js +++ b/packages/block-library/src/spacer/edit.native.js @@ -11,7 +11,7 @@ import { withPreferredColorScheme } from '@wordpress/compose'; import { InspectorControls, isValueSpacingPreset, - useSetting, + useSettings, getCustomValueFromPreset, getPxFromCssUnit, } from '@wordpress/block-editor'; @@ -39,10 +39,11 @@ const Spacer = ( { fontSize: DEFAULT_FONT_SIZE, }; const { height, width } = attributes; - const spacingSizes = [ - { name: 0, slug: '0', size: 0 }, - ...( useSetting( 'spacing.spacingSizes' ) || [] ), - ]; + const spacingSizes = [ { name: 0, slug: '0', size: 0 } ]; + const [ settingsSizes ] = useSettings( 'spacing.spacingSizes' ); + if ( settingsSizes ) { + spacingSizes.push( ...settingsSizes ); + } const { orientation } = context; const defaultStyle = getStylesFromColorScheme( styles.staticSpacer, diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js index 4dbec86fff520c..a52c68bc6a7390 100644 --- a/packages/block-library/src/tag-cloud/edit.js +++ b/packages/block-library/src/tag-cloud/edit.js @@ -18,7 +18,7 @@ import { __ } from '@wordpress/i18n'; import { InspectorControls, useBlockProps, - useSetting, + useSettings, } from '@wordpress/block-editor'; import ServerSideRender from '@wordpress/server-side-render'; import { store as coreStore } from '@wordpress/core-data'; @@ -49,13 +49,9 @@ function TagCloudEdit( { attributes, setAttributes, taxonomies } ) { largestFontSize, } = attributes; + const [ availableUnits ] = useSettings( 'spacing.units' ); const units = useCustomUnits( { - availableUnits: useSetting( 'spacing.units' ) || [ - '%', - 'px', - 'em', - 'rem', - ], + availableUnits: availableUnits || [ '%', 'px', 'em', 'rem' ], } ); const getTaxonomyOptions = () => { diff --git a/packages/block-library/src/template-part/block.json b/packages/block-library/src/template-part/block.json index 9fe431150ae392..3b0946718bcb9c 100644 --- a/packages/block-library/src/template-part/block.json +++ b/packages/block-library/src/template-part/block.json @@ -23,7 +23,8 @@ "supports": { "align": true, "html": false, - "reusable": false + "reusable": false, + "renaming": false }, "editorStyle": "wp-block-template-part-editor" } diff --git a/packages/block-library/src/template-part/edit/inner-blocks.js b/packages/block-library/src/template-part/edit/inner-blocks.js index b17428bbdbb40e..146877ee0287cc 100644 --- a/packages/block-library/src/template-part/edit/inner-blocks.js +++ b/packages/block-library/src/template-part/edit/inner-blocks.js @@ -5,7 +5,7 @@ import { useEntityBlockEditor } from '@wordpress/core-data'; import { InnerBlocks, useInnerBlocksProps, - useSetting, + useSettings, store as blockEditorStore, } from '@wordpress/block-editor'; import { useSelect } from '@wordpress/data'; @@ -21,8 +21,8 @@ export default function TemplatePartInnerBlocks( { const { getSettings } = select( blockEditorStore ); return getSettings()?.supportsLayout; }, [] ); - const defaultLayout = useSetting( 'layout' ) || {}; - const usedLayout = !! layout && layout.inherit ? defaultLayout : layout; + const [ defaultLayout ] = useSettings( 'layout' ); + const usedLayout = layout?.inherit ? defaultLayout || {} : layout; const [ blocks, onInput, onChange ] = useEntityBlockEditor( 'postType', diff --git a/packages/block-library/src/template-part/index.php b/packages/block-library/src/template-part/index.php index a7bd4033affc34..3ad400906945b8 100644 --- a/packages/block-library/src/template-part/index.php +++ b/packages/block-library/src/template-part/index.php @@ -18,13 +18,10 @@ function render_block_core_template_part( $attributes ) { $template_part_id = null; $content = null; $area = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; + $theme = isset( $attributes['theme'] ) ? $attributes['theme'] : get_stylesheet(); - if ( - isset( $attributes['slug'] ) && - isset( $attributes['theme'] ) && - get_stylesheet() === $attributes['theme'] - ) { - $template_part_id = $attributes['theme'] . '//' . $attributes['slug']; + if ( isset( $attributes['slug'] ) && get_stylesheet() === $theme ) { + $template_part_id = $theme . '//' . $attributes['slug']; $template_part_query = new WP_Query( array( 'post_type' => 'wp_template_part', @@ -34,7 +31,7 @@ function render_block_core_template_part( $attributes ) { array( 'taxonomy' => 'wp_theme', 'field' => 'name', - 'terms' => $attributes['theme'], + 'terms' => $theme, ), ), 'posts_per_page' => 1, diff --git a/packages/block-library/src/term-description/block.json b/packages/block-library/src/term-description/block.json index beee12eb674b03..fc91a4aff4c484 100644 --- a/packages/block-library/src/term-description/block.json +++ b/packages/block-library/src/term-description/block.json @@ -1,7 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, - "__experimental": "fse", "name": "core/term-description", "title": "Term Description", "category": "theme", diff --git a/packages/block-serialization-default-parser/CHANGELOG.md b/packages/block-serialization-default-parser/CHANGELOG.md index 6d05c5b37fa8fb..871ec167efdee0 100644 --- a/packages/block-serialization-default-parser/CHANGELOG.md +++ b/packages/block-serialization-default-parser/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.44.0 (2023-10-18) + ## 4.43.0 (2023-10-05) ## 4.42.0 (2023-09-20) diff --git a/packages/block-serialization-default-parser/class-wp-block-parser.php b/packages/block-serialization-default-parser/class-wp-block-parser.php index 5790e97f8cf298..543f53691ccb17 100644 --- a/packages/block-serialization-default-parser/class-wp-block-parser.php +++ b/packages/block-serialization-default-parser/class-wp-block-parser.php @@ -48,14 +48,6 @@ class WP_Block_Parser { */ public $stack; - /** - * Empty associative array, here due to PHP quirks - * - * @since 4.4.0 - * @var array empty associative array - */ - public $empty_attrs; - /** * Parses a document and returns a list of block structures * @@ -69,11 +61,10 @@ class WP_Block_Parser { * @return array[] */ public function parse( $document ) { - $this->document = $document; - $this->offset = 0; - $this->output = array(); - $this->stack = array(); - $this->empty_attrs = array(); + $this->document = $document; + $this->offset = 0; + $this->output = array(); + $this->stack = array(); while ( $this->proceed() ) { continue; @@ -287,7 +278,7 @@ public function next_token() { */ $attrs = $has_attrs ? json_decode( $matches['attrs'][0], /* as-associative */ true ) - : $this->empty_attrs; + : array(); /* * This state isn't allowed @@ -318,7 +309,7 @@ public function next_token() { * @return WP_Block_Parser_Block freeform block object. */ public function freeform( $inner_html ) { - return new WP_Block_Parser_Block( null, $this->empty_attrs, array(), $inner_html, array( $inner_html ) ); + return new WP_Block_Parser_Block( null, array(), array(), $inner_html, array( $inner_html ) ); } /** diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index 2aa3ba1a4038f6..80e1399de39175 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "4.43.0", + "version": "4.44.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-spec-parser/CHANGELOG.md b/packages/block-serialization-spec-parser/CHANGELOG.md index 71d0b962feddd0..b59c4e01cfb8b3 100644 --- a/packages/block-serialization-spec-parser/CHANGELOG.md +++ b/packages/block-serialization-spec-parser/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.44.0 (2023-10-18) + ## 4.43.0 (2023-10-05) ## 4.42.0 (2023-09-20) diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 9001a2cca57c75..fdd80bdabf118b 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-spec-parser", - "version": "4.43.0", + "version": "4.44.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index a27eb954f1b93c..42eaa3e45c246f 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 12.21.0 (2023-10-18) + ## 12.20.0 (2023-10-05) ## 12.19.0 (2023-09-20) diff --git a/packages/blocks/package.json b/packages/blocks/package.json index c36c3d52e944f7..6e20a2af9848df 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "12.20.0", + "version": "12.21.0", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index 7112ae25ccdde2..53333ef688085f 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -580,11 +580,19 @@ export function switchToBlockType( blocks, name ) { * @return {Object} block. */ export const getBlockFromExample = ( name, example ) => { - return createBlock( - name, - example.attributes, - ( example.innerBlocks ?? [] ).map( ( innerBlock ) => - getBlockFromExample( innerBlock.name, innerBlock ) - ) - ); + try { + return createBlock( + name, + example.attributes, + ( example.innerBlocks ?? [] ).map( ( innerBlock ) => + getBlockFromExample( innerBlock.name, innerBlock ) + ) + ); + } catch { + return createBlock( 'core/missing', { + originalName: name, + originalContent: '', + originalUndelimitedContent: '', + } ); + } }; diff --git a/packages/blocks/src/api/raw-handling/ms-list-converter.js b/packages/blocks/src/api/raw-handling/ms-list-converter.js index 8b45a5ab53fdb7..fdbc48398a1cc6 100644 --- a/packages/blocks/src/api/raw-handling/ms-list-converter.js +++ b/packages/blocks/src/api/raw-handling/ms-list-converter.js @@ -3,6 +3,12 @@ */ const { parseInt } = window; +/** + * Internal dependencies + */ +import { deepFilterHTML } from './utils'; +import msListIgnore from './ms-list-ignore'; + function isList( node ) { return node.nodeName === 'OL' || node.nodeName === 'UL'; } @@ -14,23 +20,10 @@ export default function msListConverter( node, doc ) { const style = node.getAttribute( 'style' ); - if ( ! style ) { - return; - } - - // Quick check. - if ( style.indexOf( 'mso-list' ) === -1 ) { - return; - } - - const matches = /mso-list\s*:[^;]+level([0-9]+)/i.exec( style ); - - if ( ! matches ) { + if ( ! style || ! style.includes( 'mso-list' ) ) { return; } - let level = parseInt( matches[ 1 ], 10 ) - 1 || 0; - const prevNode = node.previousElementSibling; // Add new list if no previous. @@ -53,13 +46,11 @@ export default function msListConverter( node, doc ) { let receivingNode = listNode; - // Remove the first span with list info. - node.removeChild( node.firstChild ); - // Add content. - while ( node.firstChild ) { - listItem.appendChild( node.firstChild ); - } + listItem.innerHTML = deepFilterHTML( node.innerHTML, [ msListIgnore ] ); + + const matches = /mso-list\s*:[^;]+level([0-9]+)/i.exec( style ); + let level = matches ? parseInt( matches[ 1 ], 10 ) - 1 || 0 : 0; // Change pointer depending on indentation level. while ( level-- ) { diff --git a/packages/blocks/src/api/raw-handling/ms-list-ignore.js b/packages/blocks/src/api/raw-handling/ms-list-ignore.js new file mode 100644 index 00000000000000..d1ed421e3b76c5 --- /dev/null +++ b/packages/blocks/src/api/raw-handling/ms-list-ignore.js @@ -0,0 +1,27 @@ +/** + * Looks for comments, and removes them. + * + * @param {Node} node The node to be processed. + * @return {void} + */ +export default function msListIgnore( node ) { + if ( node.nodeType !== node.ELEMENT_NODE ) { + return; + } + + const style = node.getAttribute( 'style' ); + + if ( ! style || ! style.includes( 'mso-list' ) ) { + return; + } + + const rules = style.split( ';' ).reduce( ( acc, rule ) => { + const [ key, value ] = rule.split( ':' ); + acc[ key.trim().toLowerCase() ] = value.trim().toLowerCase(); + return acc; + }, {} ); + + if ( rules[ 'mso-list' ] === 'ignore' ) { + node.remove(); + } +} diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index c63b207c1dbad6..9fa87462d8a1b2 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -17,6 +17,7 @@ import isInlineContent from './is-inline-content'; import phrasingContentReducer from './phrasing-content-reducer'; import headRemover from './head-remover'; import msListConverter from './ms-list-converter'; +import msListIgnore from './ms-list-ignore'; import listReducer from './list-reducer'; import imageCorrector from './image-corrector'; import blockquoteNormaliser from './blockquote-normaliser'; @@ -49,6 +50,7 @@ function filterInlineHTML( HTML, preserveWhiteSpace ) { HTML = deepFilterHTML( HTML, [ headRemover, googleDocsUIDRemover, + msListIgnore, phrasingContentReducer, commentRemover, ] ); diff --git a/packages/blocks/src/api/raw-handling/test/ms-list-converter.js b/packages/blocks/src/api/raw-handling/test/ms-list-converter.js index a7c58dfa03010a..5ae7da68f16a92 100644 --- a/packages/blocks/src/api/raw-handling/test/ms-list-converter.js +++ b/packages/blocks/src/api/raw-handling/test/ms-list-converter.js @@ -7,7 +7,7 @@ import { deepFilterHTML } from '../utils'; describe( 'msListConverter', () => { it( 'should convert unordered list', () => { const input = - '

* test

'; + '

* test

'; const output = '
  • test
'; expect( deepFilterHTML( input, [ msListConverter ] ) ).toEqual( output @@ -16,7 +16,7 @@ describe( 'msListConverter', () => { it( 'should convert ordered list', () => { const input = - '

1 test

'; + '

1 test

'; const output = '
  1. test
'; expect( deepFilterHTML( input, [ msListConverter ] ) ).toEqual( output @@ -25,11 +25,11 @@ describe( 'msListConverter', () => { it( 'should convert indented list', () => { const input1 = - '

* test

'; + '

* test

'; const input2 = - '

* test

'; + '

* test

'; const input3 = - '

* test

'; + '

* test

'; const output = '
  • test
    • test
  • test
'; expect( @@ -39,13 +39,13 @@ describe( 'msListConverter', () => { it( 'should convert deep indented list', () => { const input1 = - '

* test

'; + '

* test

'; const input2 = - '

* test

'; + '

* test

'; const input3 = - '

* test

'; + '

* test

'; const input4 = - '

* test

'; + '

* test

'; const output = '
  • test
    • test
      • test
  • test
'; expect( diff --git a/packages/blocks/src/api/test/factory.js b/packages/blocks/src/api/test/factory.js index 84d7161659928a..22d069205113bf 100644 --- a/packages/blocks/src/api/test/factory.js +++ b/packages/blocks/src/api/test/factory.js @@ -17,6 +17,7 @@ import { findTransform, isWildcardBlockTransform, isContainerGroupBlock, + getBlockFromExample, } from '../factory'; import { getBlockType, @@ -2276,4 +2277,33 @@ describe( 'block factory', () => { expect( isContainerGroupBlock( 'core/group' ) ).toBe( false ); } ); } ); + + describe( 'getBlockFromExample', () => { + it( 'should replace unregistered block with core/missing block', () => { + registerBlockType( 'core/missing', { + title: 'Unsupported', + } ); + registerBlockType( 'core/paragraph', { + title: 'Paragraph', + } ); + registerBlockType( 'core/group', { + title: 'A block that groups other blocks.', + } ); + const example = { + innerBlocks: [ + { name: 'core/paragraph' }, + { name: 'core/image' }, + ], + }; + expect( + getBlockFromExample( 'core/group', example ) + ).toMatchObject( { + name: 'core/group', + innerBlocks: [ + { name: 'core/paragraph' }, + { name: 'core/missing' }, + ], + } ); + } ); + } ); } ); diff --git a/packages/blocks/src/lock-unlock.js b/packages/blocks/src/lock-unlock.js index 363b51af7d233e..0a98fcfb19d296 100644 --- a/packages/blocks/src/lock-unlock.js +++ b/packages/blocks/src/lock-unlock.js @@ -5,6 +5,6 @@ import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/pri export const { lock, unlock } = __dangerousOptInToUnstableAPIsOnlyForCoreModules( - 'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.', + 'I know using unstable features means my theme or plugin will inevitably break in the next version of WordPress.', '@wordpress/blocks' ); diff --git a/packages/browserslist-config/CHANGELOG.md b/packages/browserslist-config/CHANGELOG.md index be36d5dacde2ea..e8920dd3541ec3 100644 --- a/packages/browserslist-config/CHANGELOG.md +++ b/packages/browserslist-config/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.27.0 (2023-10-18) + ## 5.26.0 (2023-10-05) ## 5.25.0 (2023-09-20) diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index dddefa8aa4382b..06cf5cd24c89ca 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "5.26.0", + "version": "5.27.0", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/commands/CHANGELOG.md b/packages/commands/CHANGELOG.md index 5dcd137dd95f87..9f6a2e38e0dee5 100644 --- a/packages/commands/CHANGELOG.md +++ b/packages/commands/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 0.15.0 (2023-10-18) + ## 0.14.0 (2023-10-05) ## 0.13.0 (2023-09-20) diff --git a/packages/commands/package.json b/packages/commands/package.json index 8b278cc3635bea..445307575567d3 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/commands", - "version": "0.14.0", + "version": "0.15.0", "description": "Handles the commands menu.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/commands/src/lock-unlock.js b/packages/commands/src/lock-unlock.js index 0665114d842c34..e11bd687d87421 100644 --- a/packages/commands/src/lock-unlock.js +++ b/packages/commands/src/lock-unlock.js @@ -5,6 +5,6 @@ import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/pri export const { lock, unlock } = __dangerousOptInToUnstableAPIsOnlyForCoreModules( - 'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.', + 'I know using unstable features means my theme or plugin will inevitably break in the next version of WordPress.', '@wordpress/commands' ); diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 2df99e7413ffa6..59a535447bf0dc 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,9 +2,44 @@ ## Unreleased +### Enhancements + +- `InputControl`/`SelectControl`: update `height`/`min-height` to `32px` instead of `30px` to align with modern sizing scale ([#55490](https://github.com/WordPress/gutenberg/pull/55490)). + ### Bug Fix +- `Autocomplete`: Add `aria-live` announcements for Mac and IOS Voiceover to fix lack of support for `aria-owns` ([#54902](https://github.com/WordPress/gutenberg/pull/54902)). + +### Internal + +- Introduce experimental new version of `DropdownMenu` based on `ariakit` ([#54939](https://github.com/WordPress/gutenberg/pull/54939)) + +## 25.10.0 (2023-10-18) + +### Enhancements + +- `ProgressBar`: use text color to ensure enough contrast against background ([#55285](https://github.com/WordPress/gutenberg/pull/55285)). +- `Notice`: Remove margins from `Notice` component ([#54800](https://github.com/WordPress/gutenberg/pull/54800)). +- Allow using CSS level 4 viewport-relative units ([54415](https://github.com/WordPress/gutenberg/pull/54415)) +- `ToolsPanel`: do not apply the `className` to prop to `ToolsPanelItem` components when rendered as placeholders ([#55207](https://github.com/WordPress/gutenberg/pull/55207)). +- `GradientPicker`: remove overflow styles and padding from `ColorPicker` popovers ([#55265](https://github.com/WordPress/gutenberg/pull/55265)). +- `Tabs`: Expose via private APIs ([#55327](https://github.com/WordPress/gutenberg/pull/55327)). +- `ColorPalette`/`ToggleGroupControl/ToggleGroupControlOptionBase`: add `type="button"` attribute to native `