diff --git a/backport-changelog/6.6/6756.md b/backport-changelog/6.6/6756.md new file mode 100644 index 00000000000000..e60e128bf45cb4 --- /dev/null +++ b/backport-changelog/6.6/6756.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6756 + +* https://github.com/WordPress/gutenberg/pull/62461 diff --git a/bin/packages/check-build-type-declaration-files.js b/bin/packages/check-build-type-declaration-files.js index 74c7cfa5e3c2ac..3d70145ceb588e 100644 --- a/bin/packages/check-build-type-declaration-files.js +++ b/bin/packages/check-build-type-declaration-files.js @@ -70,7 +70,7 @@ async function getDecFile( packagePath ) { async function typecheckDeclarations( file ) { return new Promise( ( resolve, reject ) => { exec( - `npx tsc --target esnext --moduleResolution node --noEmit ${ file }`, + `npx tsc --target esnext --moduleResolution node --noEmit "${ file }"`, ( error, stdout, stderr ) => { if ( error ) { reject( { file, error, stderr, stdout } ); diff --git a/lib/block-supports/block-style-variations.php b/lib/block-supports/block-style-variations.php index e09bd4ce90bf0e..f2bc6af92e9dec 100644 --- a/lib/block-supports/block-style-variations.php +++ b/lib/block-supports/block-style-variations.php @@ -209,9 +209,6 @@ function gutenberg_render_block_style_variation_class_name( $block_content, $blo /** * Collects block style variation data for merging with theme.json data. - * As each block style variation is processed it is registered if it hasn't - * been already. This registration is required for later sanitization of - * theme.json data. * * @since 6.6.0 * @@ -219,14 +216,13 @@ function gutenberg_render_block_style_variation_class_name( $block_content, $blo * * @return array Block variations data to be merged under `styles.blocks`. */ -function gutenberg_resolve_and_register_block_style_variations( $variations ) { +function gutenberg_resolve_block_style_variations( $variations ) { $variations_data = array(); if ( empty( $variations ) ) { return $variations_data; } - $registry = WP_Block_Styles_Registry::get_instance(); $have_named_variations = ! wp_is_numeric_array( $variations ); foreach ( $variations as $key => $variation ) { @@ -248,23 +244,9 @@ function gutenberg_resolve_and_register_block_style_variations( $variations ) { * Block style variations read in via standalone theme.json partials * need to have their name set to the kebab case version of their title. */ - $variation_name = $have_named_variations ? $key : _wp_to_kebab_case( $variation['title'] ); - $variation_label = $variation['title'] ?? $variation_name; + $variation_name = $have_named_variations ? $key : _wp_to_kebab_case( $variation['title'] ); foreach ( $supported_blocks as $block_type ) { - $registered_styles = $registry->get_registered_styles_for_block( $block_type ); - - // Register block style variation if it hasn't already been registered. - if ( ! array_key_exists( $variation_name, $registered_styles ) ) { - gutenberg_register_block_style( - $block_type, - array( - 'name' => $variation_name, - 'label' => $variation_label, - ) - ); - } - // Add block style variation data under current block type. $path = array( $block_type, 'variations', $variation_name ); _wp_array_set( $variations_data, $path, $variation_data ); @@ -320,7 +302,7 @@ function gutenberg_merge_block_style_variations_data( $variations_data, $theme_j function gutenberg_resolve_block_style_variations_from_theme_style_variation( $theme_json ) { $theme_json_data = $theme_json->get_data(); $shared_variations = $theme_json_data['styles']['blocks']['variations'] ?? array(); - $variations_data = gutenberg_resolve_and_register_block_style_variations( $shared_variations ); + $variations_data = gutenberg_resolve_block_style_variations( $shared_variations ); return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json, 'user' ); } @@ -337,7 +319,7 @@ function gutenberg_resolve_block_style_variations_from_theme_style_variation( $t */ function gutenberg_resolve_block_style_variations_from_theme_json_partials( $theme_json ) { $block_style_variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( 'block' ); - $variations_data = gutenberg_resolve_and_register_block_style_variations( $block_style_variations ); + $variations_data = gutenberg_resolve_block_style_variations( $block_style_variations ); return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json ); } @@ -355,7 +337,7 @@ function gutenberg_resolve_block_style_variations_from_theme_json_partials( $the function gutenberg_resolve_block_style_variations_from_primary_theme_json( $theme_json ) { $theme_json_data = $theme_json->get_data(); $block_style_variations = $theme_json_data['styles']['blocks']['variations'] ?? array(); - $variations_data = gutenberg_resolve_and_register_block_style_variations( $block_style_variations ); + $variations_data = gutenberg_resolve_block_style_variations( $block_style_variations ); return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json ); } @@ -401,6 +383,31 @@ function gutenberg_enqueue_block_style_variation_styles() { // Register the block support. WP_Block_Supports::get_instance()->register( 'block-style-variation', array() ); +// Remove core filters and action. +if ( function_exists( 'wp_render_block_style_variation_support_styles' ) ) { + remove_filter( 'render_block_data', 'wp_render_block_style_variation_support_styles' ); +} +if ( function_exists( 'wp_render_block_style_variation_class_name' ) ) { + remove_filter( 'render_block', 'wp_render_block_style_variation_class_name' ); +} +if ( function_exists( 'wp_enqueue_block_style_variation_styles' ) ) { + remove_action( 'wp_enqueue_scripts', 'wp_enqueue_block_style_variation_styles' ); +} + +if ( function_exists( 'wp_resolve_block_style_variations_from_primary_theme_json' ) ) { + remove_filter( 'wp_theme_json_data_theme', 'wp_resolve_block_style_variations_from_primary_theme_json' ); +} +if ( function_exists( 'wp_resolve_block_style_variations_from_theme_json_partials' ) ) { + remove_filter( 'wp_theme_json_data_theme', 'wp_resolve_block_style_variations_from_theme_json_partials' ); +} +if ( function_exists( 'wp_resolve_block_style_variations_from_styles_registry' ) ) { + remove_filter( 'wp_theme_json_data_theme', 'wp_resolve_block_style_variations_from_styles_registry' ); +} +if ( function_exists( 'wp_resolve_block_style_variations_from_theme_style_variation' ) ) { + remove_filter( 'wp_theme_json_data_user', 'wp_resolve_block_style_variations_from_theme_style_variation' ); +} + +// Add Gutenberg filters and action. add_filter( 'render_block_data', 'gutenberg_render_block_style_variation_support_styles', 10, 2 ); add_filter( 'render_block', 'gutenberg_render_block_style_variation_class_name', 10, 2 ); add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_block_style_variation_styles', 1 ); @@ -411,3 +418,101 @@ function gutenberg_enqueue_block_style_variation_styles() { add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_styles_registry', 10, 1 ); add_filter( 'wp_theme_json_data_user', 'gutenberg_resolve_block_style_variations_from_theme_style_variation', 10, 1 ); + + +/** + * Registers any block style variations contained within the provided + * theme.json data. + * + * @access private + * + * @param array $variations Shared block style variations. + */ +function gutenberg_register_block_style_variations_from_theme_json_data( $variations ) { + if ( empty( $variations ) ) { + return; + } + + $registry = WP_Block_Styles_Registry::get_instance(); + $have_named_variations = ! wp_is_numeric_array( $variations ); + + foreach ( $variations as $key => $variation ) { + $supported_blocks = $variation['blockTypes'] ?? array(); + + /* + * Standalone theme.json partial files for block style variations + * will have their styles under a top-level property by the same name. + * Variations defined within an existing theme.json or theme style + * variation will themselves already be the required styles data. + */ + $variation_data = $variation['styles'] ?? $variation; + + if ( empty( $variation_data ) ) { + continue; + } + + /* + * Block style variations read in via standalone theme.json partials + * need to have their name set to the kebab case version of their title. + */ + $variation_name = $have_named_variations ? $key : _wp_to_kebab_case( $variation['title'] ); + $variation_label = $variation['title'] ?? $variation_name; + + foreach ( $supported_blocks as $block_type ) { + $registered_styles = $registry->get_registered_styles_for_block( $block_type ); + + // Register block style variation if it hasn't already been registered. + if ( ! array_key_exists( $variation_name, $registered_styles ) ) { + register_block_style( + $block_type, + array( + 'name' => $variation_name, + 'label' => $variation_label, + ) + ); + } + } + } +} + +/** + * Register shared block style variations defined by the theme. + * + * These can come in three forms: + * - the theme's theme.json + * - the theme's partials (standalone files in `/styles` that only define block style variations) + * - the user's theme.json (for example, theme style variations the user selected) + * + * @access private + */ +function gutenberg_register_block_style_variations_from_theme() { + // Partials from `/styles`. + $variations_partials = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( 'block' ); + gutenberg_register_block_style_variations_from_theme_json_data( $variations_partials ); + + /* + * Pull the data from the specific origin instead of the merged data. + * This is because, for 6.6, we only support registering block style variations + * for the 'theme' and 'custom' origins but not for 'default' (core theme.json) + * or 'custom' (theme.json in a block). + * + * When/If we add support for every origin, we should switch to using the public API + * instead, e.g.: wp_get_global_styles( array( 'blocks', 'variations' ) ). + */ + + // theme.json of the theme. + $theme_json_theme = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data(); + $variations_theme = $theme_json_theme->get_data()['styles']['blocks']['variations'] ?? array(); + gutenberg_register_block_style_variations_from_theme_json_data( $variations_theme ); + + // User data linked for this theme. + $theme_json_user = WP_Theme_JSON_Resolver_Gutenberg::get_user_data(); + $variations_user = $theme_json_user->get_data()['styles']['blocks']['variations'] ?? array(); + gutenberg_register_block_style_variations_from_theme_json_data( $variations_user ); +} + +// Remove core init action registering variations. +if ( function_exists( 'wp_register_block_style_variations_from_theme' ) ) { + remove_action( 'init', 'wp_register_block_style_variations_from_theme' ); +} +add_action( 'init', 'gutenberg_register_block_style_variations_from_theme' ); diff --git a/lib/class-wp-theme-json-data-gutenberg.php b/lib/class-wp-theme-json-data-gutenberg.php index 4ff7775f4b559c..2c7eec9d5dbf4a 100644 --- a/lib/class-wp-theme-json-data-gutenberg.php +++ b/lib/class-wp-theme-json-data-gutenberg.php @@ -72,7 +72,7 @@ public function get_data() { /** * Return theme JSON object. * - * @since 18.3.0 + * @since 18.5.0 * * @return WP_Theme_JSON */ diff --git a/lib/experimental/posts/load.php b/lib/experimental/posts/load.php index 56a600ab97c05d..3fff8a151dfefc 100644 --- a/lib/experimental/posts/load.php +++ b/lib/experimental/posts/load.php @@ -14,7 +14,7 @@ function gutenberg_posts_dashboard() { wp_register_style( 'wp-gutenberg-posts-dashboard', gutenberg_url( 'build/edit-site/posts.css', __FILE__ ), - array() + array( 'wp-components', 'wp-commands' ) ); wp_enqueue_style( 'wp-gutenberg-posts-dashboard' ); wp_add_inline_script( 'wp-edit-site', 'window.wp.editSite.initializePostsDashboard( "gutenberg-posts-dashboard" );', 'after' ); diff --git a/package-lock.json b/package-lock.json index 7e20ec0fa393dd..4f2901c8fe8bd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54118,6 +54118,7 @@ "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", @@ -69267,6 +69268,7 @@ "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", diff --git a/packages/block-editor/src/components/block-bindings-toolbar-indicator/style.scss b/packages/block-editor/src/components/block-bindings-toolbar-indicator/style.scss deleted file mode 100644 index f37276290ca713..00000000000000 --- a/packages/block-editor/src/components/block-bindings-toolbar-indicator/style.scss +++ /dev/null @@ -1,12 +0,0 @@ -.block-editor-block-bindings-toolbar-indicator__popover .components-popover__content { - min-width: 260px; - padding: $grid-unit-20; -} - -.block-editor-block-bindings-toolbar-indicator .block-editor-block-bindings-toolbar-indicator-icon.has-colors svg { - fill: var(--wp-block-synced-color); -} - -.editor-collapsible-block-toolbar .block-editor-block-bindings-toolbar-indicator { - height: 32px; -} diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 8b01907474d23a..87970d53c19f42 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -1,12 +1,14 @@ /** * WordPress dependencies */ -import { __, _n, sprintf } from '@wordpress/i18n'; +import { __, _n, sprintf, _x } from '@wordpress/i18n'; import { DropdownMenu, ToolbarButton, ToolbarGroup, ToolbarItem, + __experimentalText as Text, + MenuGroup, } from '@wordpress/components'; import { switchToBlockType, @@ -33,6 +35,7 @@ function BlockSwitcherDropdownMenuContents( { clientIds, hasBlockStyles, canRemove, + isUsingBindings, } ) { const { replaceBlocks, multiSelect, updateBlockAttributes } = useDispatch( blockEditorStore ); @@ -118,6 +121,17 @@ function BlockSwitcherDropdownMenuContents( {

); } + + const connectedBlockDescription = isSingleBlock + ? _x( + 'This block is connected.', + 'block toolbar button label and description' + ) + : _x( + 'These blocks are connected.', + 'block toolbar button label and description' + ); + return (
{ hasPatternTransformation && ( @@ -156,11 +170,18 @@ function BlockSwitcherDropdownMenuContents( { onSwitch={ onClose } /> ) } + { isUsingBindings && ( + + + { connectedBlockDescription } + + + ) }
); } -export const BlockSwitcher = ( { clientIds, disabled } ) => { +export const BlockSwitcher = ( { clientIds, disabled, isUsingBindings } ) => { const { canRemove, hasBlockStyles, @@ -303,6 +324,7 @@ export const BlockSwitcher = ( { clientIds, disabled } ) => { clientIds={ clientIds } hasBlockStyles={ hasBlockStyles } canRemove={ canRemove } + isUsingBindings={ isUsingBindings } /> ) } diff --git a/packages/block-editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss index 2072616f7fb0f7..5eaba08bf94ae2 100644 --- a/packages/block-editor/src/components/block-switcher/style.scss +++ b/packages/block-editor/src/components/block-switcher/style.scss @@ -203,3 +203,8 @@ padding: 6px $grid-unit; margin: 0; } + +.block-editor-block-switcher__binding-indicator { + display: block; + padding: $grid-unit; +} diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index 0e341d32163952..cffb46413c5bbb 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -35,7 +35,7 @@ import { store as blockEditorStore } from '../../store'; import __unstableBlockNameContext from './block-name-context'; import NavigableToolbar from '../navigable-toolbar'; import Shuffle from './shuffle'; -import BlockBindingsIndicator from '../block-bindings-toolbar-indicator'; +import PatternOverridesToolbarIndicator from '../pattern-overrides-toolbar-indicator'; import { useHasBlockToolbar } from './use-has-block-toolbar'; import { canBindBlock } from '../../hooks/use-bindings-attributes'; /** @@ -67,11 +67,13 @@ export function PrivateBlockToolbar( { shouldShowVisualToolbar, showParentSelector, isUsingBindings, + hasParentPattern, } = useSelect( ( select ) => { const { getBlockName, getBlockMode, getBlockParents, + getBlockParentsByBlockName, getSelectedBlockClientIds, isBlockValid, getBlockRootClientId, @@ -94,8 +96,13 @@ export function PrivateBlockToolbar( { const isVisual = selectedBlockClientIds.every( ( id ) => getBlockMode( id ) === 'visual' ); - const _isUsingBindings = !! getBlockAttributes( selectedBlockClientId ) - ?.metadata?.bindings; + const bindings = getBlockAttributes( selectedBlockClientId )?.metadata + ?.bindings; + const parentPatternClientId = getBlockParentsByBlockName( + selectedBlockClientId, + 'core/block', + true + )[ 0 ]; return { blockClientId: selectedBlockClientId, blockClientIds: selectedBlockClientIds, @@ -115,7 +122,8 @@ export function PrivateBlockToolbar( { ) && selectedBlockClientIds.length === 1 && _isDefaultEditingMode, - isUsingBindings: _isUsingBindings, + isUsingBindings: !! bindings, + hasParentPattern: !! parentPatternClientId, }; }, [] ); @@ -146,6 +154,7 @@ export function PrivateBlockToolbar( { const innerClasses = clsx( 'block-editor-block-toolbar', { 'is-synced': isSynced, + 'is-connected': isUsingBindings, } ); return ( @@ -167,9 +176,13 @@ export function PrivateBlockToolbar( { { ! isMultiToolbar && isLargeViewport && isDefaultEditingMode && } - { isUsingBindings && canBindBlock( blockName ) && ( - - ) } + { isUsingBindings && + hasParentPattern && + canBindBlock( blockName ) && ( + + ) } { ( shouldShowVisualToolbar || isMultiToolbar ) && ( isDefaultEditingMode || isSynced ) && (
{ isDefaultEditingMode && ( <> diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index 1854e440329647..6314f1a1e7ffd5 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -39,14 +39,17 @@ border-right: $border-width solid $gray-300; } - &.is-synced .block-editor-block-switcher .components-button .block-editor-block-icon { - color: var(--wp-block-synced-color); - } - - &.is-synced .components-toolbar-button.block-editor-block-switcher__no-switcher-icon { - &:disabled .block-editor-block-icon.has-colors { + &.is-synced, + &.is-connected { + .block-editor-block-switcher .components-button .block-editor-block-icon { color: var(--wp-block-synced-color); } + + .components-toolbar-button.block-editor-block-switcher__no-switcher-icon { + &:disabled .block-editor-block-icon.has-colors { + color: var(--wp-block-synced-color); + } + } } > :last-child, diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js index 7f3c3ed27e667f..307c742befafda 100644 --- a/packages/block-editor/src/components/global-styles/background-panel.js +++ b/packages/block-editor/src/components/global-styles/background-panel.js @@ -23,7 +23,7 @@ import { __experimentalHStack as HStack, __experimentalTruncate as Truncate, } from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; +import { __, _x, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { getFilename } from '@wordpress/url'; import { useCallback, Platform, useRef } from '@wordpress/element'; @@ -544,17 +544,26 @@ function BackgroundSizeToolsPanelItem( { diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index f93ae2a63787c2..4db12c4d8b77c2 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -181,6 +181,12 @@ function ListViewBlock( { return; } + // Do not handle events if it comes from modals; + // retain the default behavior for these keys. + if ( event.target.closest( '[role=dialog]' ) ) { + return; + } + const isDeleteKey = [ BACKSPACE, DELETE ].includes( event.keyCode ); // If multiple blocks are selected, deselect all blocks when the user @@ -196,12 +202,6 @@ function ListViewBlock( { isDeleteKey || isMatch( 'core/block-editor/remove', event ) ) { - // Do not handle single-key block deletion shortcuts when events come from modals; - // retain the default behavior for these keys. - if ( isDeleteKey && event.target.closest( '[role=dialog]' ) ) { - return; - } - const { blocksToUpdate: blocksToDelete, firstBlockClientId, diff --git a/packages/block-editor/src/components/list-view/branch.js b/packages/block-editor/src/components/list-view/branch.js index 16480927dc4665..ebd1d5a36f5700 100644 --- a/packages/block-editor/src/components/list-view/branch.js +++ b/packages/block-editor/src/components/list-view/branch.js @@ -194,10 +194,14 @@ function ListViewBranch( props ) { // This prevents the entire tree from being rendered when a branch is // selected, or a user selects all blocks, while still enabling scroll // into view behavior when selecting a block or opening the list view. + // The first and last blocks of the list are always rendered, to ensure + // that Home and End keys work as expected. const showBlock = isDragged || blockInView || - ( isSelected && clientId === selectedClientIds[ 0 ] ); + ( isSelected && clientId === selectedClientIds[ 0 ] ) || + index === 0 || + index === blockCount - 1; return ( { showBlock && ( diff --git a/packages/block-editor/src/components/block-bindings-toolbar-indicator/index.js b/packages/block-editor/src/components/pattern-overrides-toolbar-indicator/index.js similarity index 62% rename from packages/block-editor/src/components/block-bindings-toolbar-indicator/index.js rename to packages/block-editor/src/components/pattern-overrides-toolbar-indicator/index.js index e25c489c1dbf91..af359da542d37a 100644 --- a/packages/block-editor/src/components/block-bindings-toolbar-indicator/index.js +++ b/packages/block-editor/src/components/pattern-overrides-toolbar-indicator/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { useId } from '@wordpress/element'; -import { __, sprintf, _x } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { DropdownMenu, ToolbarGroup, @@ -20,15 +20,18 @@ import { store as blockEditorStore } from '../../store'; import BlockIcon from '../block-icon'; import useBlockDisplayTitle from '../block-title/use-block-display-title'; -export default function BlockBindingsToolbarIndicator( { clientIds } ) { +/** + * This component is currently only for pattern overrides, which is a WP-only feature. + * Ideally, this should be moved to the `patterns` package once ready. + * @param {Object} props The component props. + * @param {Array} props.clientIds The client IDs of the selected blocks. + */ +export default function PatternOverridesToolbarIndicator( { clientIds } ) { const isSingleBlockSelected = clientIds.length === 1; - const { icon, firstBlockName, isConnectedToPatternOverrides } = useSelect( + const { icon, firstBlockName } = useSelect( ( select ) => { - const { - getBlockAttributes, - getBlockNamesByClientId, - getBlocksByClientId, - } = select( blockEditorStore ); + const { getBlockAttributes, getBlockNamesByClientId } = + select( blockEditorStore ); const { getBlockType, getActiveBlockVariation } = select( blocksStore ); const blockTypeNames = getBlockNamesByClientId( clientIds ); @@ -54,16 +57,6 @@ export default function BlockBindingsToolbarIndicator( { clientIds } ) { icon: _icon, firstBlockName: getBlockAttributes( clientIds[ 0 ] ).metadata .name, - isConnectedToPatternOverrides: getBlocksByClientId( - clientIds - ).some( ( block ) => - Object.values( - block?.attributes?.metadata?.bindings || {} - ).some( - ( binding ) => - binding.source === 'core/pattern-overrides' - ) - ), }; }, [ clientIds, isSingleBlockSelected ] @@ -73,25 +66,15 @@ export default function BlockBindingsToolbarIndicator( { clientIds } ) { maximumLength: 35, } ); - let blockDescription = isSingleBlockSelected - ? _x( - 'This block is connected.', - 'block toolbar button label and description' + const blockDescription = isSingleBlockSelected + ? sprintf( + /* translators: %1s: The block type's name; %2s: The block's user-provided name (the same as the override name). */ + __( 'This %1$s is editable using the "%2$s" override.' ), + firstBlockTitle.toLowerCase(), + firstBlockName ) - : _x( - 'These blocks are connected.', - 'block toolbar button label and description' - ); - if ( isConnectedToPatternOverrides && firstBlockName ) { - blockDescription = isSingleBlockSelected - ? sprintf( - /* translators: %1s: The block type's name; %2s: The block's user-provided name (the same as the override name). */ - __( 'This %1$s is editable using the "%2$s" override.' ), - firstBlockTitle.toLowerCase(), - firstBlockName - ) - : __( 'These blocks are editable using overrides.' ); - } + : __( 'These blocks are editable using overrides.' ); + const descriptionId = useId(); return ( @@ -99,18 +82,18 @@ export default function BlockBindingsToolbarIndicator( { clientIds } ) { { ( toggleProps ) => ( diff --git a/packages/block-editor/src/components/pattern-overrides-toolbar-indicator/style.scss b/packages/block-editor/src/components/pattern-overrides-toolbar-indicator/style.scss new file mode 100644 index 00000000000000..90b2c1cdd79a5e --- /dev/null +++ b/packages/block-editor/src/components/pattern-overrides-toolbar-indicator/style.scss @@ -0,0 +1,12 @@ +.block-editor-pattern-overrides-toolbar-indicator__popover .components-popover__content { + min-width: 260px; + padding: $grid-unit-20; +} + +.block-editor-pattern-overrides-toolbar-indicator .block-editor-pattern-overrides-toolbar-indicator-icon.has-colors svg { + fill: var(--wp-block-synced-color); +} + +.editor-collapsible-block-toolbar .block-editor-pattern-overrides-toolbar-indicator { + height: 32px; +} diff --git a/packages/block-editor/src/hooks/block-style-variation.js b/packages/block-editor/src/hooks/block-style-variation.js index ee02b97d216704..311997d46f0ada 100644 --- a/packages/block-editor/src/hooks/block-style-variation.js +++ b/packages/block-editor/src/hooks/block-style-variation.js @@ -43,7 +43,7 @@ function getVariationNameFromClass( className, registeredStyles = [] ) { return null; } -function useBlockSyleVariation( name, variation, clientId ) { +function useBlockStyleVariation( name, variation, clientId ) { // Prefer global styles data in GlobalStylesContext, which are available // if in the site editor. Otherwise fall back to whatever is in the // editor settings and available in the post editor. @@ -96,7 +96,7 @@ function useBlockProps( { name, className, clientId } ) { const variation = getVariationNameFromClass( className, registeredStyles ); const variationClass = `is-style-${ variation }-${ clientId }`; - const { settings, styles } = useBlockSyleVariation( + const { settings, styles } = useBlockStyleVariation( name, variation, clientId diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 484d79e8db9fa0..d22ea9b3d0a283 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -1,6 +1,6 @@ @import "./autocompleters/style.scss"; @import "./components/block-alignment-control/style.scss"; -@import "./components/block-bindings-toolbar-indicator/style.scss"; +@import "./components/pattern-overrides-toolbar-indicator/style.scss"; @import "./components/block-canvas/style.scss"; @import "./components/block-icon/style.scss"; @import "./components/block-inspector/style.scss"; diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss index 9f22262c53bd95..c62cbe9614c916 100644 --- a/packages/block-library/src/navigation/editor.scss +++ b/packages/block-library/src/navigation/editor.scss @@ -457,12 +457,6 @@ $color-control-label-height: 20px; } } -.has-fixed-toolbar .wp-block-navigation__responsive-container.is-menu-open { - @include break-medium() { - top: $admin-bar-height + $header-height + $block-toolbar-height + $border-width; - } -} - .is-mobile-preview .wp-block-navigation__responsive-container.is-menu-open, .is-tablet-preview .wp-block-navigation__responsive-container.is-menu-open { top: $admin-bar-height + $header-height + $block-toolbar-height + $border-width; @@ -479,12 +473,6 @@ $color-control-label-height: 20px; } } - .has-fixed-toolbar .wp-block-navigation__responsive-container.is-menu-open { - @include break-medium() { - top: $header-height + $block-toolbar-height + $border-width; - } - } - .is-mobile-preview .wp-block-navigation__responsive-container.is-menu-open, .is-tablet-preview .wp-block-navigation__responsive-container.is-menu-open { top: $header-height + $block-toolbar-height + $border-width; diff --git a/packages/dataviews/src/index.ts b/packages/dataviews/src/index.ts index 233ecd6864a5d5..31f44e5ed97502 100644 --- a/packages/dataviews/src/index.ts +++ b/packages/dataviews/src/index.ts @@ -1,3 +1,4 @@ export { default as DataViews } from './dataviews'; export { VIEW_LAYOUTS } from './layouts'; export { filterSortAndPaginate } from './filter-and-sort-data-view'; +export type * from './types'; diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss index 0390a360f66d27..5844e0c9133369 100644 --- a/packages/dataviews/src/style.scss +++ b/packages/dataviews/src/style.scss @@ -436,6 +436,7 @@ .components-button { opacity: 0; position: fixed; + right: 0; } } diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 3934ab8cda8848..5065e4b903040e 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fixes + +- Add ´@wordpress/html-entities´ package to the list of dependencies in package.json. ([#62313](https://github.com/WordPress/gutenberg/pull/62313)) + ## 8.0.0 (2024-05-31) ### Breaking Changes diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index b3327218dfc873..e293e459b6ac0e 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -44,6 +44,7 @@ "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index e20732cb646bca..cfaaf810b41918 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -34,6 +34,7 @@ import { import { privateApis as coreCommandsPrivateApis } from '@wordpress/core-commands'; import { privateApis as blockLibraryPrivateApis } from '@wordpress/block-library'; import { addQueryArgs } from '@wordpress/url'; +import { decodeEntities } from '@wordpress/html-entities'; import { store as coreStore } from '@wordpress/core-data'; import { SlotFillProvider } from '@wordpress/components'; @@ -288,7 +289,7 @@ function Layout( { sprintf( // translators: %s: Title of the created post e.g: "Post 1". __( '"%s" successfully created.' ), - title + decodeEntities( title ) ), { type: 'snackbar', diff --git a/packages/edit-site/src/components/add-new-page/index.js b/packages/edit-site/src/components/add-new-page/index.js index 56544c83f491bb..a1b19298a12433 100644 --- a/packages/edit-site/src/components/add-new-page/index.js +++ b/packages/edit-site/src/components/add-new-page/index.js @@ -13,6 +13,7 @@ import { useDispatch } from '@wordpress/data'; import { useState } from '@wordpress/element'; import { store as coreStore } from '@wordpress/core-data'; import { store as noticesStore } from '@wordpress/notices'; +import { decodeEntities } from '@wordpress/html-entities'; export default function AddNewPageModal( { onSave, onClose } ) { const [ isCreatingPage, setIsCreatingPage ] = useState( false ); @@ -47,7 +48,7 @@ export default function AddNewPageModal( { onSave, onClose } ) { sprintf( // translators: %s: Title of the created template e.g: "Category". __( '"%s" successfully created.' ), - newPage.title?.rendered || title + decodeEntities( newPage.title?.rendered || title ) ), { type: 'snackbar', diff --git a/packages/edit-site/src/components/app/index.js b/packages/edit-site/src/components/app/index.js index 764b188acf6a53..452c36014f5db9 100644 --- a/packages/edit-site/src/components/app/index.js +++ b/packages/edit-site/src/components/app/index.js @@ -17,10 +17,26 @@ import { privateApis as routerPrivateApis } from '@wordpress/router'; */ import Layout from '../layout'; import { unlock } from '../../lock-unlock'; +import { useCommonCommands } from '../../hooks/commands/use-common-commands'; +import { useEditModeCommands } from '../../hooks/commands/use-edit-mode-commands'; +import useInitEditedEntityFromURL from '../sync-state-with-url/use-init-edited-entity-from-url'; +import useLayoutAreas from '../layout/router'; +import useSetCommandContext from '../../hooks/commands/use-set-command-context'; const { RouterProvider } = unlock( routerPrivateApis ); const { GlobalStylesProvider } = unlock( editorPrivateApis ); +function AppLayout() { + // This ensures the edited entity id and type are initialized properly. + useInitEditedEntityFromURL(); + useEditModeCommands(); + useCommonCommands(); + useSetCommandContext(); + const route = useLayoutAreas(); + + return ; +} + export default function App() { const { createErrorNotice } = useDispatch( noticesStore ); @@ -41,7 +57,7 @@ export default function App() { - + diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 50fb05d8d841f8..96bf2fad4d6b68 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -21,6 +21,7 @@ import { useCallback, useMemo } from '@wordpress/element'; import { store as noticesStore } from '@wordpress/notices'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { store as preferencesStore } from '@wordpress/preferences'; +import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies @@ -146,7 +147,7 @@ export default function EditSiteEditor( { isLoading } ) { sprintf( // translators: %s: Title of the created post e.g: "Post 1". __( '"%s" successfully created.' ), - _title + decodeEntities( _title ) ), { type: 'snackbar', diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 58de32d4b46878..72d48122057ac9 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -21,91 +21,60 @@ import { import { __ } from '@wordpress/i18n'; import { useState, useRef, useEffect } from '@wordpress/element'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; -import { - CommandMenu, - privateApis as commandsPrivateApis, -} from '@wordpress/commands'; -import { store as preferencesStore } from '@wordpress/preferences'; -import { - privateApis as blockEditorPrivateApis, - store as blockEditorStore, -} from '@wordpress/block-editor'; -import { privateApis as coreCommandsPrivateApis } from '@wordpress/core-commands'; +import { CommandMenu } from '@wordpress/commands'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; import { EditorSnackbars, privateApis as editorPrivateApis, } from '@wordpress/editor'; +import { privateApis as coreCommandsPrivateApis } from '@wordpress/core-commands'; /** * Internal dependencies */ import ErrorBoundary from '../error-boundary'; import { store as editSiteStore } from '../../store'; -import useInitEditedEntityFromURL from '../sync-state-with-url/use-init-edited-entity-from-url'; import SiteHub from '../site-hub'; import ResizableFrame from '../resizable-frame'; -import useSyncCanvasModeWithURL from '../sync-state-with-url/use-sync-canvas-mode-with-url'; import { unlock } from '../../lock-unlock'; import SavePanel from '../save-panel'; import KeyboardShortcutsRegister from '../keyboard-shortcuts/register'; import KeyboardShortcutsGlobal from '../keyboard-shortcuts/global'; -import { useCommonCommands } from '../../hooks/commands/use-common-commands'; -import { useEditModeCommands } from '../../hooks/commands/use-edit-mode-commands'; import { useIsSiteEditorLoading } from './hooks'; -import useLayoutAreas from './router'; import useMovingAnimation from './animation'; import SidebarContent from '../sidebar'; import SaveHub from '../save-hub'; +import useSyncCanvasModeWithURL from '../sync-state-with-url/use-sync-canvas-mode-with-url'; const { useCommands } = unlock( coreCommandsPrivateApis ); -const { useCommandContext } = unlock( commandsPrivateApis ); const { useGlobalStyle } = unlock( blockEditorPrivateApis ); const { NavigableRegion } = unlock( editorPrivateApis ); const ANIMATION_DURATION = 0.3; -export default function Layout() { - // This ensures the edited entity id and type are initialized properly. - useInitEditedEntityFromURL(); +export default function Layout( { route } ) { useSyncCanvasModeWithURL(); useCommands(); - useEditModeCommands(); - useCommonCommands(); - const isMobileViewport = useViewportMatch( 'medium', '<' ); const toggleRef = useRef(); - const { - isDistractionFree, - hasFixedToolbar, - hasBlockSelected, - canvasMode, - previousShortcut, - nextShortcut, - } = useSelect( ( select ) => { - const { getAllShortcutKeyCombinations } = select( - keyboardShortcutsStore - ); - const { getCanvasMode } = unlock( select( editSiteStore ) ); - return { - canvasMode: getCanvasMode(), - previousShortcut: getAllShortcutKeyCombinations( - 'core/editor/previous-region' - ), - nextShortcut: getAllShortcutKeyCombinations( - 'core/editor/next-region' - ), - hasFixedToolbar: select( preferencesStore ).get( - 'core', - 'fixedToolbar' - ), - isDistractionFree: select( preferencesStore ).get( - 'core', - 'distractionFree' - ), - hasBlockSelected: - select( blockEditorStore ).getBlockSelectionStart(), - }; - }, [] ); + const { canvasMode, previousShortcut, nextShortcut } = useSelect( + ( select ) => { + const { getAllShortcutKeyCombinations } = select( + keyboardShortcutsStore + ); + const { getCanvasMode } = unlock( select( editSiteStore ) ); + return { + canvasMode: getCanvasMode(), + previousShortcut: getAllShortcutKeyCombinations( + 'core/editor/previous-region' + ), + nextShortcut: getAllShortcutKeyCombinations( + 'core/editor/next-region' + ), + }; + }, + [] + ); const navigateRegionsProps = useNavigateRegions( { previous: previousShortcut, next: nextShortcut, @@ -116,22 +85,11 @@ export default function Layout() { const isEditorLoading = useIsSiteEditorLoading(); const [ isResizableFrameOversized, setIsResizableFrameOversized ] = useState( false ); - const { key: routeKey, areas, widths } = useLayoutAreas(); + const { key: routeKey, areas, widths } = route; const animationRef = useMovingAnimation( { triggerAnimationOnChange: canvasMode + '__' + routeKey, } ); - // Sets the right context for the command palette - let commandContext = 'site-editor'; - - if ( canvasMode === 'edit' ) { - commandContext = 'entity-edit'; - } - if ( hasBlockSelected ) { - commandContext = 'block-selection-edit'; - } - useCommandContext( commandContext ); - const [ backgroundColor ] = useGlobalStyle( 'color.background' ); const [ gradientValue ] = useGlobalStyle( 'color.gradient' ); const previousCanvaMode = usePrevious( canvasMode ); @@ -163,11 +121,7 @@ export default function Layout() { 'edit-site-layout', navigateRegionsProps.className, { - 'is-distraction-free': - isDistractionFree && canvasMode === 'edit', 'is-full-canvas': canvasMode === 'edit', - 'has-fixed-toolbar': hasFixedToolbar, - 'is-block-toolbar-visible': hasBlockSelected, } ) } > diff --git a/packages/edit-site/src/components/posts-app/index.js b/packages/edit-site/src/components/posts-app/index.js new file mode 100644 index 00000000000000..3d803f0241d78b --- /dev/null +++ b/packages/edit-site/src/components/posts-app/index.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { + UnsavedChangesWarning, + privateApis as editorPrivateApis, +} from '@wordpress/editor'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import Layout from '../layout'; +import Page from '../page'; +import { unlock } from '../../lock-unlock'; + +const { RouterProvider } = unlock( routerPrivateApis ); +const { GlobalStylesProvider } = unlock( editorPrivateApis ); + +const defaultRoute = { + key: 'index', + areas: { + sidebar: 'Empty Sidebar', + content: Welcome to Posts, + preview: undefined, + mobile: Welcome to Posts, + }, +}; + +export default function PostsApp() { + return ( + + + + + + + ); +} diff --git a/packages/edit-site/src/hooks/commands/use-set-command-context.js b/packages/edit-site/src/hooks/commands/use-set-command-context.js new file mode 100644 index 00000000000000..2824a6cc99f1c3 --- /dev/null +++ b/packages/edit-site/src/hooks/commands/use-set-command-context.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { privateApis as commandsPrivateApis } from '@wordpress/commands'; +import { store as blockEditorStore } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { store as editSiteStore } from '../../store'; +import { unlock } from '../../lock-unlock'; + +const { useCommandContext } = unlock( commandsPrivateApis ); + +/** + * React hook used to set the correct command context based on the current state. + */ +export default function useSetCommandContext() { + const { hasBlockSelected, canvasMode } = useSelect( ( select ) => { + const { getCanvasMode } = unlock( select( editSiteStore ) ); + const { getBlockSelectionStart } = select( blockEditorStore ); + return { + canvasMode: getCanvasMode(), + hasBlockSelected: getBlockSelectionStart(), + }; + }, [] ); + // Sets the right context for the command palette + let commandContext = 'site-editor'; + if ( canvasMode === 'edit' ) { + commandContext = 'entity-edit'; + } + if ( hasBlockSelected ) { + commandContext = 'block-selection-edit'; + } + useCommandContext( commandContext ); +} diff --git a/packages/edit-site/src/posts.js b/packages/edit-site/src/posts.js index ceee039806c9e7..19e787fff46305 100644 --- a/packages/edit-site/src/posts.js +++ b/packages/edit-site/src/posts.js @@ -3,6 +3,11 @@ */ import { createRoot, StrictMode } from '@wordpress/element'; +/** + * Internal dependencies + */ +import PostsApp from './components/posts-app'; + /** * Initializes the "Posts Dashboard" * @param {string} id DOM element id. @@ -14,7 +19,11 @@ export function initializePostsDashboard( id ) { const target = document.getElementById( id ); const root = createRoot( target ); - root.render( Welcome To Posts ); + root.render( + + + + ); return root; } diff --git a/packages/edit-site/src/posts.scss b/packages/edit-site/src/posts.scss index 777ee3fa3412d5..4150634913cb7c 100644 --- a/packages/edit-site/src/posts.scss +++ b/packages/edit-site/src/posts.scss @@ -1,3 +1,14 @@ +@import "../../dataviews/src/style.scss"; +@import "./components/layout/style.scss"; +@import "./components/page/style.scss"; +@import "./components/save-hub/style.scss"; +@import "./components/save-panel/style.scss"; +@import "./components/sidebar/style.scss"; +@import "./components/site-hub/style.scss"; +@import "./components/site-icon/style.scss"; +@import "./components/editor-canvas-container/style.scss"; +@import "./components/resizable-frame/style.scss"; + @include wordpress-admin-schemes(); #wpadminbar, @@ -7,13 +18,29 @@ #wpcontent { margin-left: 0; } +body.js #wpbody { + padding-top: 0; +} body { @include wp-admin-reset("#gutenberg-posts-dashboard"); +} +#gutenberg-posts-dashboard { @include reset; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - min-height: 100vh; + height: 100vh; + + // On mobile the main content area has to scroll, otherwise you can invoke + // the over-scroll bounce on the non-scrolling container, for a bad experience. + @include break-small { + bottom: 0; + left: 0; + min-height: 100vh; + position: fixed; + right: 0; + top: 0; + } + + .no-js & { + min-height: 0; + position: static; + } } diff --git a/packages/editor/README.md b/packages/editor/README.md index b9106822272424..8279ec3e743605 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -1504,6 +1504,18 @@ _Returns_ Undocumented declaration. +### registerEntityAction + +Registers a new DataViews action. + +This is an experimental API and is subject to change. it's only available in the Gutenberg plugin for now. + +_Parameters_ + +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _config_ `Action`: Action configuration. + ### RichText > **Deprecated** since 5.3, use `wp.blockEditor.RichText` instead. @@ -1589,6 +1601,18 @@ Undocumented declaration. Undocumented declaration. +### unregisterEntityAction + +Unregisters a DataViews action. + +This is an experimental API and is subject to change. it's only available in the Gutenberg plugin for now. + +_Parameters_ + +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _actionId_ `string`: Action ID. + ### UnsavedChangesWarning Warns the user if there are unsaved changes before leaving the editor. Compatible with Post Editor and Site Editor. diff --git a/packages/editor/src/components/block-settings-menu/content-only-settings-menu.js b/packages/editor/src/components/block-settings-menu/content-only-settings-menu.js index 4683dd38593a59..8527dfff4f7523 100644 --- a/packages/editor/src/components/block-settings-menu/content-only-settings-menu.js +++ b/packages/editor/src/components/block-settings-menu/content-only-settings-menu.js @@ -19,7 +19,7 @@ import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; function ContentOnlySettingsMenuItems( { clientId, onClose } ) { - const { entity, onNavigateToEntityRecord } = useSelect( + const { entity, onNavigateToEntityRecord, canEditTemplates } = useSelect( ( select ) => { const { getBlockEditingMode, @@ -46,11 +46,12 @@ function ContentOnlySettingsMenuItems( { clientId, onClose } ) { getBlockAttributes( patternParent ).ref ); } else { - const { getCurrentPostType, getCurrentTemplateId } = - select( editorStore ); - const currentPostType = getCurrentPostType(); + const { getCurrentTemplateId } = select( editorStore ); const templateId = getCurrentTemplateId(); - if ( currentPostType === 'page' && templateId ) { + const { getContentLockingParent } = unlock( + select( blockEditorStore ) + ); + if ( ! getContentLockingParent( clientId ) && templateId ) { record = select( coreStore ).getEntityRecord( 'postType', 'wp_template', @@ -58,7 +59,12 @@ function ContentOnlySettingsMenuItems( { clientId, onClose } ) { ); } } + const _canEditTemplates = select( coreStore ).canUser( + 'create', + 'templates' + ); return { + canEditTemplates: _canEditTemplates, entity: record, onNavigateToEntityRecord: getSettings().onNavigateToEntityRecord, @@ -77,6 +83,19 @@ function ContentOnlySettingsMenuItems( { clientId, onClose } ) { } const isPattern = entity.type === 'wp_block'; + let helpText = isPattern + ? __( + 'Edit the pattern to move, delete, or make further changes to this block.' + ) + : __( + 'Edit the template to move, delete, or make further changes to this block.' + ); + + if ( ! canEditTemplates ) { + helpText = __( + 'Only users with permissions to edit the template can move or delete this block' + ); + } return ( <> @@ -88,6 +107,7 @@ function ContentOnlySettingsMenuItems( { clientId, onClose } ) { postType: entity.type, } ); } } + disabled={ ! canEditTemplates } > { isPattern ? __( 'Edit pattern' ) : __( 'Edit template' ) } @@ -97,13 +117,7 @@ function ContentOnlySettingsMenuItems( { clientId, onClose } ) { as="p" className="editor-content-only-settings-menu__description" > - { isPattern - ? __( - 'Edit the pattern to move, delete, or make further changes to this block.' - ) - : __( - 'Edit the template to move, delete, or make further changes to this block.' - ) } + { helpText } ); diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 69337e181f5e55..6dcc7f6cb66708 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -783,7 +783,7 @@ const duplicatePostAction = { sprintf( // translators: %s: Title of the created template e.g: "Category". __( '"%s" successfully created.' ), - newItem.title?.rendered || title + decodeEntities( newItem.title?.rendered || title ) ), { id: 'duplicate-post-action', @@ -1030,11 +1030,13 @@ export const duplicateTemplatePartAction = { }; export function usePostActions( postType, onActionPerformed ) { - const { postTypeObject } = useSelect( + const { defaultActions, postTypeObject } = useSelect( ( select ) => { const { getPostType } = select( coreStore ); + const { getEntityActions } = unlock( select( editorStore ) ); return { postTypeObject: getPostType( postType ), + defaultActions: getEntityActions( 'postType', postType ), }; }, [ postType ] @@ -1072,6 +1074,7 @@ export function usePostActions( postType, onActionPerformed ) { ? deletePostAction : trashPostAction, ! isTemplateOrTemplatePart && permanentlyDeletePostAction, + ...defaultActions, ].filter( Boolean ); if ( onActionPerformed ) { @@ -1117,6 +1120,7 @@ export function usePostActions( postType, onActionPerformed ) { return actions; }, [ + defaultActions, isTemplateOrTemplatePart, isPattern, postTypeObject?.viewable, diff --git a/packages/editor/src/components/post-title/index.js b/packages/editor/src/components/post-title/index.js index 57ab39f0061615..08bd07f4b72dbe 100644 --- a/packages/editor/src/components/post-title/index.js +++ b/packages/editor/src/components/post-title/index.js @@ -30,14 +30,12 @@ import usePostTitle from './use-post-title'; import PostTypeSupportCheck from '../post-type-support-check'; function PostTitle( _, forwardedRef ) { - const { placeholder, hasFixedToolbar } = useSelect( ( select ) => { + const { placeholder } = useSelect( ( select ) => { const { getSettings } = select( blockEditorStore ); - const { titlePlaceholder, hasFixedToolbar: _hasFixedToolbar } = - getSettings(); + const { titlePlaceholder } = getSettings(); return { placeholder: titlePlaceholder, - hasFixedToolbar: _hasFixedToolbar, }; }, [] ); @@ -186,7 +184,6 @@ function PostTitle( _, forwardedRef ) { // This same block is used in both the visual and the code editor. const className = clsx( DEFAULT_CLASSNAMES, { 'is-selected': isSelected, - 'has-fixed-toolbar': hasFixedToolbar, } ); return ( diff --git a/packages/editor/src/components/post-title/post-title-raw.js b/packages/editor/src/components/post-title/post-title-raw.js index a4c9713a094925..66c944b45871ab 100644 --- a/packages/editor/src/components/post-title/post-title-raw.js +++ b/packages/editor/src/components/post-title/post-title-raw.js @@ -29,14 +29,12 @@ import usePostTitle from './use-post-title'; * @return {Component} The rendered component. */ function PostTitleRaw( _, forwardedRef ) { - const { placeholder, hasFixedToolbar } = useSelect( ( select ) => { + const { placeholder } = useSelect( ( select ) => { const { getSettings } = select( blockEditorStore ); - const { titlePlaceholder, hasFixedToolbar: _hasFixedToolbar } = - getSettings(); + const { titlePlaceholder } = getSettings(); return { placeholder: titlePlaceholder, - hasFixedToolbar: _hasFixedToolbar, }; }, [] ); @@ -61,7 +59,6 @@ function PostTitleRaw( _, forwardedRef ) { // This same block is used in both the visual and the code editor. const className = clsx( DEFAULT_CLASSNAMES, { 'is-selected': isSelected, - 'has-fixed-toolbar': hasFixedToolbar, 'is-raw-text': true, } ); diff --git a/packages/editor/src/dataviews/api.js b/packages/editor/src/dataviews/api.js new file mode 100644 index 00000000000000..130a69bba754c7 --- /dev/null +++ b/packages/editor/src/dataviews/api.js @@ -0,0 +1,55 @@ +/** + * WordPress dependencies + */ +import { dispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { unlock } from '../lock-unlock'; +import { store as editorStore } from '../store'; + +/** + * @typedef {import('@wordpress/dataviews').Action} Action + */ + +/** + * Registers a new DataViews action. + * + * This is an experimental API and is subject to change. + * it's only available in the Gutenberg plugin for now. + * + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {Action} config Action configuration. + */ + +export function registerEntityAction( kind, name, config ) { + const { registerEntityAction: _registerEntityAction } = unlock( + dispatch( editorStore ) + ); + + if ( globalThis.IS_GUTENBERG_PLUGIN ) { + _registerEntityAction( kind, name, config ); + } +} + +/** + * Unregisters a DataViews action. + * + * This is an experimental API and is subject to change. + * it's only available in the Gutenberg plugin for now. + * + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {string} actionId Action ID. + */ +export function unregisterEntityAction( kind, name, actionId ) { + const { unregisterEntityAction: _unregisterEntityAction } = unlock( + dispatch( editorStore ) + ); + + if ( globalThis.IS_GUTENBERG_PLUGIN ) { + _unregisterEntityAction( kind, name, actionId ); + } +} diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts new file mode 100644 index 00000000000000..a74e1b5e79844a --- /dev/null +++ b/packages/editor/src/dataviews/store/private-actions.ts @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import type { Action, AnyItem } from '@wordpress/dataviews'; + +export function registerEntityAction< Item extends AnyItem >( + kind: string, + name: string, + config: Action< Item > +) { + return { + type: 'REGISTER_ENTITY_ACTION' as const, + kind, + name, + config, + }; +} + +export function unregisterEntityAction( + kind: string, + name: string, + actionId: string +) { + return { + type: 'UNREGISTER_ENTITY_ACTION' as const, + kind, + name, + actionId, + }; +} diff --git a/packages/editor/src/dataviews/store/private-selectors.ts b/packages/editor/src/dataviews/store/private-selectors.ts new file mode 100644 index 00000000000000..938228ad97ed72 --- /dev/null +++ b/packages/editor/src/dataviews/store/private-selectors.ts @@ -0,0 +1,8 @@ +/** + * Internal dependencies + */ +import type { State } from './reducer'; + +export function getEntityActions( state: State, kind: string, name: string ) { + return state.actions[ kind ]?.[ name ] ?? []; +} diff --git a/packages/editor/src/dataviews/store/reducer.ts b/packages/editor/src/dataviews/store/reducer.ts new file mode 100644 index 00000000000000..e3b400faabe934 --- /dev/null +++ b/packages/editor/src/dataviews/store/reducer.ts @@ -0,0 +1,44 @@ +/** + * WordPress dependencies + */ +import { combineReducers } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import type { Action } from '@wordpress/dataviews'; + +type ReduxAction = + | ReturnType< typeof import('./private-actions').registerEntityAction > + | ReturnType< typeof import('./private-actions').unregisterEntityAction >; + +export type ActionState = Record< string, Record< string, Action< any >[] > >; +export type State = { + actions: ActionState; +}; + +function actions( state: ActionState = {}, action: ReduxAction ) { + switch ( action.type ) { + case 'REGISTER_ENTITY_ACTION': + return { + ...state, + [ action.kind ]: { + [ action.name ]: [ + ...( state[ action.kind ]?.[ action.name ] ?? [] ), + action.config, + ], + }, + }; + case 'UNREGISTER_ENTITY_ACTION': { + return { + ...state, + [ action.kind ]: ( + state[ action.kind ]?.[ action.name ] ?? [] + ).filter( ( _action ) => _action.id !== action.actionId ), + }; + } + } + + return state; +} + +export default combineReducers( { + actions, +} ); diff --git a/packages/editor/src/index.js b/packages/editor/src/index.js index 3f6d7a78d837c0..1f7bb7699c7040 100644 --- a/packages/editor/src/index.js +++ b/packages/editor/src/index.js @@ -8,6 +8,7 @@ export { storeConfig, store } from './store'; export * from './components'; export * from './utils'; export * from './private-apis'; +export * from './dataviews/api'; /* * Backward compatibility diff --git a/packages/editor/src/store/private-actions.js b/packages/editor/src/store/private-actions.js index 9304a2fe2c0579..bde803962f5d3a 100644 --- a/packages/editor/src/store/private-actions.js +++ b/packages/editor/src/store/private-actions.js @@ -15,6 +15,7 @@ import { decodeEntities } from '@wordpress/html-entities'; * Internal dependencies */ import isTemplateRevertable from './utils/is-template-revertable'; +export * from '../dataviews/store/private-actions'; /** * Returns an action object used to set which template is currently being used/edited. diff --git a/packages/editor/src/store/private-selectors.js b/packages/editor/src/store/private-selectors.js index 8a866b46a6cdd7..f56e1e8c9e81fb 100644 --- a/packages/editor/src/store/private-selectors.js +++ b/packages/editor/src/store/private-selectors.js @@ -27,6 +27,7 @@ import { } from './selectors'; import { TEMPLATE_PART_POST_TYPE } from './constants'; import { getFilteredTemplatePartBlocks } from './utils/get-filtered-template-parts'; +import { getEntityActions as _getEntityActions } from '../dataviews/store/private-selectors'; const EMPTY_INSERTION_POINT = { rootClientId: undefined, @@ -180,3 +181,7 @@ export const hasPostMetaChanges = createRegistrySelector( ); } ); + +export function getEntityActions( state, ...args ) { + return _getEntityActions( state.dataviews, ...args ); +} diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index f9b4e05ffa8e5e..a165d90e296d6f 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -7,6 +7,7 @@ import { combineReducers } from '@wordpress/data'; * Internal dependencies */ import { EDITOR_SETTINGS_DEFAULTS } from './defaults'; +import dataviewsReducer from '../dataviews/store/reducer'; /** * Returns a post attribute value, flattening nested rendered content using its @@ -402,4 +403,5 @@ export default combineReducers( { listViewPanel, listViewToggleRef, publishSidebarActive, + dataviews: dataviewsReducer, } ); diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json new file mode 100644 index 00000000000000..1177a1040b117b --- /dev/null +++ b/packages/editor/tsconfig.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "declarationDir": "build-types" + }, + "references": [ + { "path": "../a11y" }, + { "path": "../api-fetch" }, + { "path": "../blob" }, + { "path": "../block-editor" }, + { "path": "../components" }, + { "path": "../compose" }, + { "path": "../core-data" }, + { "path": "../data" }, + { "path": "../dataviews" }, + { "path": "../date" }, + { "path": "../deprecated" }, + { "path": "../dom" }, + { "path": "../element" }, + { "path": "../hooks" }, + { "path": "../html-entities" }, + { "path": "../i18n" }, + { "path": "../icons" }, + { "path": "../keycodes" }, + { "path": "../notices" }, + { "path": "../plugins" }, + { "path": "../private-apis" }, + { "path": "../rich-text" }, + { "path": "../url" }, + { "path": "../warning" }, + { "path": "../wordcount" } + ], + "include": [ "src/**/*.ts", "src/**/*.tsx" ] +} diff --git a/packages/icons/src/library/offline.js b/packages/icons/src/library/offline.js index 444d3667f297e8..698a42aa4badb9 100644 --- a/packages/icons/src/library/offline.js +++ b/packages/icons/src/library/offline.js @@ -5,11 +5,7 @@ import { SVG, Path } from '@wordpress/primitives'; const offline = ( - + ); diff --git a/phpunit/block-supports/block-style-variations-test.php b/phpunit/block-supports/block-style-variations-test.php index b84267446330cc..870a76a4fb4a49 100644 --- a/phpunit/block-supports/block-style-variations-test.php +++ b/phpunit/block-supports/block-style-variations-test.php @@ -63,6 +63,11 @@ public function filter_set_theme_root() { public function test_add_registered_block_styles_to_theme_data() { switch_theme( 'block-theme' ); + // Trigger block style registration that occurs on `init` action. + // do_action( 'init' ) could be used here however this direct call + // means only the updates being tested are performed. + gutenberg_register_block_style_variations_from_theme(); + $variation_styles_data = array( 'color' => array( 'background' => 'darkslateblue', diff --git a/test/e2e/specs/editor/various/list-view.spec.js b/test/e2e/specs/editor/various/list-view.spec.js index 143fea43c09eef..531faa8bea049d 100644 --- a/test/e2e/specs/editor/various/list-view.spec.js +++ b/test/e2e/specs/editor/various/list-view.spec.js @@ -1120,7 +1120,7 @@ test.describe( 'List View', () => { 'Pressing keyboard shortcut should also work when the menu is opened and focused' ) .toMatchObject( [ - { name: 'core/paragraph', selected: true, focused: false }, + { name: 'core/paragraph', selected: true, focused: true }, { name: 'core/file', selected: false, focused: false }, ] ); await expect( diff --git a/test/e2e/specs/site-editor/command-center.spec.js b/test/e2e/specs/site-editor/command-center.spec.js index 6608f37f1701b3..fce951ca767bed 100644 --- a/test/e2e/specs/site-editor/command-center.spec.js +++ b/test/e2e/specs/site-editor/command-center.spec.js @@ -43,9 +43,11 @@ test.describe( 'Site editor command palette', () => { .click(); await page.keyboard.type( 'index' ); await page.getByRole( 'option', { name: 'index' } ).click(); - await expect( page.getByRole( 'heading', { level: 1 } ) ).toHaveText( - 'Index' - ); + await expect( + page + .getByRole( 'region', { name: 'Editor top bar' } ) + .getByRole( 'heading', { level: 1 } ) + ).toHaveText( 'Index' ); } ); test( 'Open the command palette and navigate to Customize CSS', async ( { diff --git a/tsconfig.json b/tsconfig.json index 4add5beed2f436..cf986ddbee72bf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,7 @@ { "path": "packages/dom" }, { "path": "packages/dom-ready" }, { "path": "packages/e2e-test-utils-playwright" }, + { "path": "packages/editor" }, { "path": "packages/element" }, { "path": "packages/escape-html" }, { "path": "packages/eslint-plugin" },