From 467aade14ccae801b96778740666ef2298972a59 Mon Sep 17 00:00:00 2001 From: Bernie Reiter <96308+ockham@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:01:17 +0100 Subject: [PATCH 1/8] Block Hooks: Fix in Navigation block (#59021) Update the Block Hooks mechanism as used in the Navigation block upon writing the REST API response received from a client to the DB. The Block Hooks mechanism recently underwent some overhaul (in https://github.com/WordPress/wordpress-develop/pull/6087), which decoupled hooked block injection from setting the `ignoredHookedBlocks` metadata. The latter is no longer done by `insert_hooked_blocks` but by a separate function called `set_ignored_hooked_blocks_metadata` (that can be passed as a callback to the `make_{before|after}_block` visitor factories). Since the implementation of the Navigation block relied on the previous functionality, it needs to be updated to work properly: We need to run block tree traversal with `set_ignored_hooked_blocks_metadata` rather than `insert_hooked_blocks` on the template content received from the endpoint for the Navigation post type, upon persisting to the DB. Co-authored-by: ockham Co-authored-by: tjcafferkey Co-authored-by: gziolo --- .../block-library/src/navigation/index.php | 120 +++++++++++++----- 1 file changed, 88 insertions(+), 32 deletions(-) diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index f1292e5d2e723a..73b682fd6a4311 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -218,7 +218,7 @@ private static function get_inner_blocks_from_navigation_post( $attributes ) { // it encounters whitespace. This code strips it. $blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks ); - if ( function_exists( 'get_hooked_block_markup' ) ) { + if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) ) { // Run Block Hooks algorithm to inject hooked blocks. $markup = block_core_navigation_insert_hooked_blocks( $blocks, $navigation_post ); $root_nav_block = parse_blocks( $markup )[0]; @@ -1024,7 +1024,7 @@ function block_core_navigation_get_fallback_blocks() { // In this case default to the (Page List) fallback. $fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks; - if ( function_exists( 'get_hooked_block_markup' ) ) { + if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) ) { // Run Block Hooks algorithm to inject hooked blocks. // We have to run it here because we need the post ID of the Navigation block to track ignored hooked blocks. $markup = block_core_navigation_insert_hooked_blocks( $fallback_blocks, $navigation_post ); @@ -1369,25 +1369,28 @@ function block_core_navigation_get_most_recently_published_navigation() { } /** - * Insert hooked blocks into a Navigation block. - * - * Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object, - * this function inserts hooked blocks into it, and returns the serialized inner blocks in a - * mock Navigation block wrapper. + * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks. * - * If there are any hooked blocks that need to be inserted as the Navigation block's first or last - * children, the `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is checked to see if any - * of those hooked blocks should be exempted from insertion. + * @param string $serialized_block The serialized markup of a block and its inner blocks. + * @return string + */ +function block_core_navigation_remove_serialized_parent_block( $serialized_block ) { + $start = strpos( $serialized_block, '-->' ) + strlen( '-->' ); + $end = strrpos( $serialized_block, '' ) + strlen( '-->' ); - $end = strrpos( $content, ' +
+`, + status: 'publish', + } ); + + await admin.createNewPost(); + + await editor.insertBlock( { + name: 'core/block', + attributes: { ref: id }, + } ); + + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await editor.selectBlocks( imageBlock ); + await imageBlock + .getByTestId( 'form-file-upload-input' ) + .setInputFiles( TEST_IMAGE_FILE_PATH ); + await expect( imageBlock.getByRole( 'img' ) ).toHaveCount( 1 ); + await expect( imageBlock.getByRole( 'img' ) ).toHaveAttribute( + 'src', + /\/wp-content\/uploads\// + ); + + await editor.publishPost(); + await page.reload(); + + await editor.selectBlocks( imageBlock ); + await editor.showBlockToolbar(); + const blockToolbar = page.getByRole( 'toolbar', { + name: 'Block tools', + } ); + await expect( imageBlock.getByRole( 'img' ) ).toHaveAttribute( + 'src', + /\/wp-content\/uploads\// + ); + await expect( + blockToolbar.getByRole( 'button', { name: 'Replace' } ) + ).toBeEnabled(); + await expect( + blockToolbar.getByRole( 'button', { + name: 'Upload to Media Library', + } ) + ).toBeHidden(); + } ); } ); From 0dc55a842ae9d124601ae6577c4ff5f24898f428 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 20 Feb 2024 13:57:37 +1100 Subject: [PATCH 5/8] DocumentBar: Simplify component, use framer for animation (#58656) * DocumentBar: Simplify component, use framer for animation * Disable animation if reduced motion is on Co-authored-by: noisysocks Co-authored-by: ramonjd Co-authored-by: youknowriad --- .../src/components/document-bar/index.js | 203 ++++++++++-------- .../src/components/document-bar/style.scss | 37 ---- 2 files changed, 114 insertions(+), 126 deletions(-) diff --git a/packages/editor/src/components/document-bar/index.js b/packages/editor/src/components/document-bar/index.js index 2ac03a63e73fbe..84005331d18bb3 100644 --- a/packages/editor/src/components/document-bar/index.js +++ b/packages/editor/src/components/document-bar/index.js @@ -12,6 +12,8 @@ import { Button, __experimentalText as Text, __experimentalHStack as HStack, + __unstableMotion as motion, + __unstableAnimatePresence as AnimatePresence, } from '@wordpress/components'; import { BlockIcon } from '@wordpress/block-editor'; import { @@ -22,16 +24,17 @@ import { symbol, } from '@wordpress/icons'; import { displayShortcut } from '@wordpress/keycodes'; -import { useEntityRecord } from '@wordpress/core-data'; +import { store as coreStore } from '@wordpress/core-data'; import { store as commandsStore } from '@wordpress/commands'; -import { useState, useEffect, useRef } from '@wordpress/element'; +import { useRef, useEffect } from '@wordpress/element'; +import { useReducedMotion } from '@wordpress/compose'; /** * Internal dependencies */ import { store as editorStore } from '../../store'; -const typeLabels = { +const TYPE_LABELS = { // translators: 1: Pattern title. wp_pattern: __( 'Editing pattern: %s' ), // translators: 1: Navigation menu title. @@ -42,127 +45,149 @@ const typeLabels = { wp_template_part: __( 'Editing template part: %s' ), }; -const icons = { +const ICONS = { wp_block: symbol, wp_navigation: navigationIcon, }; -export default function DocumentBar() { - const { postType, postId, onNavigateToPreviousEntityRecord } = useSelect( - ( select ) => { - const { - getCurrentPostId, - getCurrentPostType, - getEditorSettings: getSettings, - } = select( editorStore ); - return { - postType: getCurrentPostType(), - postId: getCurrentPostId(), - onNavigateToPreviousEntityRecord: - getSettings().onNavigateToPreviousEntityRecord, - getEditorSettings: getSettings, - }; - }, - [] - ); +const TEMPLATE_POST_TYPES = [ 'wp_template', 'wp_template_part' ]; - const handleOnBack = () => { - if ( onNavigateToPreviousEntityRecord ) { - onNavigateToPreviousEntityRecord(); - } - }; +const GLOBAL_POST_TYPES = [ + ...TEMPLATE_POST_TYPES, + 'wp_block', + 'wp_navigation', +]; - return ( - - ); -} +const MotionButton = motion( Button ); -function BaseDocumentActions( { postType, postId, onBack } ) { - const { open: openCommandCenter } = useDispatch( commandsStore ); - const { editedRecord: doc, isResolving } = useEntityRecord( - 'postType', +export default function DocumentBar() { + const { postType, - postId - ); - const { templateIcon, templateTitle } = useSelect( ( select ) => { - const { __experimentalGetTemplateInfo: getTemplateInfo } = - select( editorStore ); - const templateInfo = getTemplateInfo( doc ); + document, + isResolving, + templateIcon, + templateTitle, + onNavigateToPreviousEntityRecord, + } = useSelect( ( select ) => { + const { + getCurrentPostType, + getCurrentPostId, + getEditorSettings, + __experimentalGetTemplateInfo: getTemplateInfo, + } = select( editorStore ); + const { getEditedEntityRecord, getIsResolving } = select( coreStore ); + const _postType = getCurrentPostType(); + const _postId = getCurrentPostId(); + const _document = getEditedEntityRecord( + 'postType', + _postType, + _postId + ); + const _templateInfo = getTemplateInfo( _document ); return { - templateIcon: templateInfo.icon, - templateTitle: templateInfo.title, + postType: _postType, + document: _document, + isResolving: getIsResolving( + 'getEditedEntityRecord', + 'postType', + _postType, + _postId + ), + templateIcon: _templateInfo.icon, + templateTitle: _templateInfo.title, + onNavigateToPreviousEntityRecord: + getEditorSettings().onNavigateToPreviousEntityRecord, }; - } ); - const isNotFound = ! doc && ! isResolving; - const icon = icons[ postType ] ?? pageIcon; - const [ isAnimated, setIsAnimated ] = useState( false ); - const isMounting = useRef( true ); - const isTemplate = [ 'wp_template', 'wp_template_part' ].includes( - postType - ); - const isGlobalEntity = [ - 'wp_template', - 'wp_navigation', - 'wp_template_part', - 'wp_block', - ].includes( postType ); + }, [] ); - useEffect( () => { - if ( ! isMounting.current ) { - setIsAnimated( true ); - } - isMounting.current = false; - }, [ postType, postId ] ); + const { open: openCommandCenter } = useDispatch( commandsStore ); + const isReducedMotion = useReducedMotion(); + + const isNotFound = ! document && ! isResolving; + const icon = ICONS[ postType ] ?? pageIcon; + const isTemplate = TEMPLATE_POST_TYPES.includes( postType ); + const isGlobalEntity = GLOBAL_POST_TYPES.includes( postType ); + const hasBackButton = !! onNavigateToPreviousEntityRecord; + const title = isTemplate ? templateTitle : document.title; - const title = isTemplate ? templateTitle : doc.title; + const mounted = useRef( false ); + useEffect( () => { + mounted.current = true; + }, [] ); return (
- { onBack && ( - - ) } - { isNotFound && { __( 'Document not found' ) } } - { ! isNotFound && ( + + { hasBackButton && ( + { + event.stopPropagation(); + onNavigateToPreviousEntityRecord(); + } } + size="compact" + initial={ + mounted.current + ? { opacity: 0, transform: 'translateX(15%)' } + : false // Don't show entry animation when DocumentBar mounts. + } + animate={ { opacity: 1, transform: 'translateX(0%)' } } + exit={ { opacity: 0, transform: 'translateX(15%)' } } + transition={ + isReducedMotion ? { duration: 0 } : undefined + } + > + { __( 'Back' ) } + + ) } + + { isNotFound ? ( + { __( 'Document not found' ) } + ) : (