diff --git a/.eslintrc.js b/.eslintrc.js
index 9240b96c033b48..e997e7804beac4 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -253,6 +253,24 @@ module.exports = {
],
},
},
+ {
+ files: [
+ 'packages/*/src/**/*.[tj]s?(x)',
+ 'storybook/stories/**/*.[tj]s?(x)',
+ ],
+ excludedFiles: [ '**/*.native.js' ],
+ rules: {
+ 'no-restricted-syntax': [
+ 'error',
+ {
+ selector:
+ 'JSXOpeningElement[name.name="Button"]:not(:has(JSXAttribute[name.name="__experimentalIsFocusable"])) JSXAttribute[name.name="disabled"]',
+ message:
+ '`disabled` used without the `__experimentalIsFocusable` prop. Disabling a control without maintaining focusability can cause accessibility issues, by hiding their presence from screen reader users, or preventing focus from returning to a trigger element. (Ignore this error if you truly mean to disable.)',
+ },
+ ],
+ },
+ },
{
files: [
// Components package.
diff --git a/docs/reference-guides/block-api/block-styles.md b/docs/reference-guides/block-api/block-styles.md
index 90b6c06d18f59d..b47b1a76a71f68 100644
--- a/docs/reference-guides/block-api/block-styles.md
+++ b/docs/reference-guides/block-api/block-styles.md
@@ -1,6 +1,6 @@
# Styles
-Block Styles allow alternative styles to be applied to existing blocks. They work by adding a className to the block's wrapper. This className can be used to provide an alternative styling for the block if the block style is selected. See the [Getting Started with JavaScript tutorial](/docs/how-to-guides/javascript/) for a full example.
+Block Styles allow alternative styles to be applied to existing blocks. They work by adding a className to the block's wrapper. This className can be used to provide an alternative styling for the block if the block style is selected. See the [Use styles and stylesheets](/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md) for a full example on how to apply styles to a block.
_Example:_
diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md
index 862a8b2d8a06aa..f687eb79732b5a 100644
--- a/docs/reference-guides/data/data-core-block-editor.md
+++ b/docs/reference-guides/data/data-core-block-editor.md
@@ -1439,7 +1439,7 @@ wp.data.dispatch( 'core/block-editor' ).registerInserterMediaCategory( {
per_page: 'page_size',
search: 'q',
};
- const url = new URL( 'https://api.openverse.engineering/v1/images/' );
+ const url = new URL( 'https://api.openverse.org/v1/images/' );
Object.entries( finalQuery ).forEach( ( [ key, value ] ) => {
const queryKey = mapFromInserterMediaRequest[ key ] || key;
url.searchParams.set( queryKey, value );
diff --git a/docs/reference-guides/slotfills/README.md b/docs/reference-guides/slotfills/README.md
index 043a50cb5186e6..ea324f71b25e83 100644
--- a/docs/reference-guides/slotfills/README.md
+++ b/docs/reference-guides/slotfills/README.md
@@ -33,7 +33,7 @@ registerPlugin( 'post-status-info-test', { render: PluginPostStatusInfoTest } );
SlotFills are created using `createSlotFill`. This creates two components, `Slot` and `Fill` which are then used to create a new component that is exported on the `wp.plugins` global.
-**Definition of the `PluginPostStatusInfo` SlotFill** ([see core code](https://github.com/WordPress/gutenberg/blob/HEAD/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js#L54))
+**Definition of the `PluginPostStatusInfo` SlotFill** ([see core code](https://github.com/WordPress/gutenberg/blob/HEAD/packages/editor/src/components/plugin-post-status-info/index.js#L55))
```js
/**
@@ -61,34 +61,70 @@ export default PluginPostStatusInfo;
This new Slot is then exposed in the editor. The example below is from core and represents the Summary panel.
As we can see, the `` is wrapping all of the items that will appear in the panel.
-Any items that have been added via the SlotFill ( see the example above ), will be included in the `fills` parameter and be displayed between the `` and `` components.
+Any items that have been added via the SlotFill ( see the example above ), will be included in the `fills` parameter and be displayed in the end of the component.
-See [core code](https://github.com/WordPress/gutenberg/tree/HEAD/packages/edit-post/src/components/sidebar/post-status/index.js#L26).
+See [core code](https://github.com/WordPress/gutenberg/tree/HEAD/packages/editor/src/components/sidebar/post-summary.js#L39).
```js
-const PostStatus = ( { isOpened, onTogglePanel } ) => (
-
-
- { ( fills ) => (
- <>
-
-
-
-
-
-
- { fills }
-
- >
- ) }
-
-
-);
+export default function PostSummary( { onActionPerformed } ) {
+ const { isRemovedPostStatusPanel } = useSelect( ( select ) => {
+ // We use isEditorPanelRemoved to hide the panel if it was programatically removed. We do
+ // not use isEditorPanelEnabled since this panel should not be disabled through the UI.
+ const { isEditorPanelRemoved, getCurrentPostType } =
+ select( editorStore );
+ return {
+ isRemovedPostStatusPanel: isEditorPanelRemoved( PANEL_NAME ),
+ postType: getCurrentPostType(),
+ };
+ }, [] );
+
+ return (
+
+
+ { ( fills ) => (
+ <>
+
+
+ }
+ />
+
+
+
+
+
+
+ { ! isRemovedPostStatusPanel && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { fills }
+
+ ) }
+
+ >
+ ) }
+
+
+ );
+}
```
## Currently available SlotFills and examples
diff --git a/lib/block-template-utils.php b/lib/block-template-utils.php
new file mode 100644
index 00000000000000..a644047d3cfdc1
--- /dev/null
+++ b/lib/block-template-utils.php
@@ -0,0 +1,114 @@
+open( $filename, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) {
+ return new WP_Error( 'unable_to_create_zip', __( 'Unable to open export file (archive) for writing.', 'gutenberg' ) );
+ }
+
+ $zip->addEmptyDir( 'templates' );
+ $zip->addEmptyDir( 'parts' );
+
+ // Get path of the theme.
+ $theme_path = wp_normalize_path( get_stylesheet_directory() );
+
+ // Create recursive directory iterator.
+ $theme_files = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator( $theme_path ),
+ RecursiveIteratorIterator::LEAVES_ONLY
+ );
+
+ // Make a copy of the current theme.
+ foreach ( $theme_files as $file ) {
+ // Skip directories as they are added automatically.
+ if ( ! $file->isDir() ) {
+ // Get real and relative path for current file.
+ $file_path = wp_normalize_path( $file );
+ $relative_path = substr( $file_path, strlen( $theme_path ) + 1 );
+
+ if ( ! wp_is_theme_directory_ignored( $relative_path ) ) {
+ $zip->addFile( $file_path, $relative_path );
+ }
+ }
+ }
+
+ // Load templates into the zip file.
+ $templates = gutenberg_get_block_templates();
+ foreach ( $templates as $template ) {
+ $template->content = traverse_and_serialize_blocks(
+ parse_blocks( $template->content ),
+ '_remove_theme_attribute_from_template_part_block'
+ );
+
+ $zip->addFromString(
+ 'templates/' . $template->slug . '.html',
+ $template->content
+ );
+ }
+
+ // Load template parts into the zip file.
+ $template_parts = gutenberg_get_block_templates( array(), 'wp_template_part' );
+ foreach ( $template_parts as $template_part ) {
+ $zip->addFromString(
+ 'parts/' . $template_part->slug . '.html',
+ $template_part->content
+ );
+ }
+
+ // Load theme.json into the zip file.
+ $tree = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data( array(), array( 'with_supports' => false ) );
+ // Merge with user data.
+ $tree->merge( WP_Theme_JSON_Resolver_Gutenberg::get_user_data() );
+
+ $theme_json_raw = $tree->get_data();
+ // If a version is defined, add a schema.
+ if ( $theme_json_raw['version'] ) {
+ $theme_json_version = 'wp/' . substr( $wp_version, 0, 3 );
+ $schema = array( '$schema' => 'https://schemas.wp.org/' . $theme_json_version . '/theme.json' );
+ $theme_json_raw = array_merge( $schema, $theme_json_raw );
+ }
+
+ // Convert to a string.
+ $theme_json_encoded = wp_json_encode( $theme_json_raw, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
+
+ // Replace 4 spaces with a tab.
+ $theme_json_tabbed = preg_replace( '~(?:^|\G)\h{4}~m', "\t", $theme_json_encoded );
+
+ // Add the theme.json file to the zip.
+ $zip->addFromString(
+ 'theme.json',
+ $theme_json_tabbed
+ );
+
+ // Save changes to the zip file.
+ $zip->close();
+
+ return $filename;
+}
diff --git a/lib/class-wp-rest-edit-site-export-controller-gutenberg.php b/lib/class-wp-rest-edit-site-export-controller-gutenberg.php
new file mode 100644
index 00000000000000..b05de230dd0ccd
--- /dev/null
+++ b/lib/class-wp-rest-edit-site-export-controller-gutenberg.php
@@ -0,0 +1,46 @@
+add_data( array( 'status' => 500 ) );
+
+ return $filename;
+ }
+
+ $theme_name = basename( get_stylesheet() );
+ header( 'Content-Type: application/zip' );
+ header( 'Content-Disposition: attachment; filename=' . $theme_name . '.zip' );
+ header( 'Content-Length: ' . filesize( $filename ) );
+ flush();
+ readfile( $filename );
+ unlink( $filename );
+ exit;
+ }
+}
diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php
index 54796685f45ab8..2cf026cc817c15 100644
--- a/lib/compat/wordpress-6.6/rest-api.php
+++ b/lib/compat/wordpress-6.6/rest-api.php
@@ -87,3 +87,73 @@ function gutenberg_register_global_styles_revisions_endpoints() {
}
add_action( 'rest_api_init', 'gutenberg_register_global_styles_revisions_endpoints' );
+
+if ( ! function_exists( 'gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field' ) ) {
+ /**
+ * Adds `stylesheet_uri` fields to WP_REST_Themes_Controller class.
+ */
+ function gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field() {
+ register_rest_field(
+ 'theme',
+ 'stylesheet_uri',
+ array(
+ 'get_callback' => function ( $item ) {
+ if ( ! empty( $item['stylesheet'] ) ) {
+ $theme = wp_get_theme( $item['stylesheet'] );
+ $current_theme = wp_get_theme();
+ if ( $theme->get_stylesheet() === $current_theme->get_stylesheet() ) {
+ return get_stylesheet_directory_uri();
+ } else {
+ return $theme->get_stylesheet_directory_uri();
+ }
+ }
+
+ return null;
+ },
+ 'schema' => array(
+ 'type' => 'string',
+ 'description' => __( 'The uri for the theme\'s stylesheet directory.', 'gutenberg' ),
+ 'format' => 'uri',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit', 'embed' ),
+ ),
+ )
+ );
+ }
+}
+add_action( 'rest_api_init', 'gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field' );
+
+if ( ! function_exists( 'gutenberg_register_wp_rest_themes_template_directory_uri_field' ) ) {
+ /**
+ * Adds `template_uri` fields to WP_REST_Themes_Controller class.
+ */
+ function gutenberg_register_wp_rest_themes_template_directory_uri_field() {
+ register_rest_field(
+ 'theme',
+ 'template_uri',
+ array(
+ 'get_callback' => function ( $item ) {
+ if ( ! empty( $item['stylesheet'] ) ) {
+ $theme = wp_get_theme( $item['stylesheet'] );
+ $current_theme = wp_get_theme();
+ if ( $theme->get_stylesheet() === $current_theme->get_stylesheet() ) {
+ return get_template_directory_uri();
+ } else {
+ return $theme->get_template_directory_uri();
+ }
+ }
+
+ return null;
+ },
+ 'schema' => array(
+ 'type' => 'string',
+ 'description' => __( 'The uri for the theme\'s template directory. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet directory.', 'gutenberg' ),
+ 'format' => 'uri',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit', 'embed' ),
+ ),
+ )
+ );
+ }
+}
+add_action( 'rest_api_init', 'gutenberg_register_wp_rest_themes_template_directory_uri_field' );
diff --git a/lib/experimental/script-modules.php b/lib/experimental/script-modules.php
index a9d5540e56dc55..709ab322f63a3e 100644
--- a/lib/experimental/script-modules.php
+++ b/lib/experimental/script-modules.php
@@ -205,10 +205,10 @@ function gutenberg_dequeue_module( $module_identifier ) {
* This embeds data in the page HTML so that it is available on page load.
*
* Data can be associated with a given Script Module by using the
- * `scriptmoduledata_{$module_id}` filter.
+ * `script_module_data_{$module_id}` filter.
*
* The data for a given Script Module will be JSON serialized in a script tag with an ID
- * like `wp-scriptmodule-data_{$module_id}`.
+ * like `wp-script-module-data-{$module_id}`.
*/
function gutenberg_print_script_module_data(): void {
$get_marked_for_enqueue = new ReflectionMethod( 'WP_Script_Modules', 'get_marked_for_enqueue' );
@@ -236,14 +236,14 @@ function gutenberg_print_script_module_data(): void {
* If the filter returns no data (an empty array), nothing will be embedded in the page.
*
* The data for a given Script Module, if provided, will be JSON serialized in a script tag
- * with an ID like `wp-scriptmodule-data_{$module_id}`.
+ * with an ID like `wp-script-module-data-{$module_id}`.
*
* The dynamic portion of the hook name, `$module_id`, refers to the Script Module ID that
* the data is associated with.
*
* @param array $data The data that should be associated with the array.
*/
- $data = apply_filters( "scriptmoduledata_{$module_id}", array() );
+ $data = apply_filters( "script_module_data_{$module_id}", array() );
if ( is_array( $data ) && ! empty( $data ) ) {
/*
@@ -281,7 +281,7 @@ function gutenberg_print_script_module_data(): void {
wp_json_encode( $data, $json_encode_flags ),
array(
'type' => 'application/json',
- 'id' => "wp-scriptmodule-data_{$module_id}",
+ 'id' => "wp-script-module-data-{$module_id}",
)
);
}
diff --git a/lib/load.php b/lib/load.php
index 23985f9c8a92e9..1f63c816f8173d 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -53,6 +53,7 @@ function gutenberg_is_experiment_enabled( $name ) {
// Plugin specific code.
require_once __DIR__ . '/class-wp-rest-global-styles-controller-gutenberg.php';
+ require_once __DIR__ . '/class-wp-rest-edit-site-export-controller-gutenberg.php';
require_once __DIR__ . '/rest-api.php';
// Experimental.
@@ -206,6 +207,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/demo.php';
require __DIR__ . '/experiments-page.php';
require __DIR__ . '/interactivity-api.php';
+require __DIR__ . '/block-template-utils.php';
if ( gutenberg_is_experiment_enabled( 'gutenberg-full-page-client-side-navigation' ) ) {
require __DIR__ . '/experimental/full-page-client-side-navigation.php';
}
diff --git a/lib/rest-api.php b/lib/rest-api.php
index 04f521d132c461..fedd75151584d5 100644
--- a/lib/rest-api.php
+++ b/lib/rest-api.php
@@ -18,3 +18,15 @@ function gutenberg_register_global_styles_endpoints() {
$global_styles_controller->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_global_styles_endpoints' );
+
+if ( ! function_exists( 'gutenberg_register_edit_site_export_controller_endpoints' ) ) {
+ /**
+ * Registers the Edit Site Export REST API routes.
+ */
+ function gutenberg_register_edit_site_export_controller_endpoints() {
+ $edit_site_export_controller = new WP_REST_Edit_Site_Export_Controller_Gutenberg();
+ $edit_site_export_controller->register_routes();
+ }
+}
+
+add_action( 'rest_api_init', 'gutenberg_register_edit_site_export_controller_endpoints' );
diff --git a/packages/block-directory/src/plugins/get-install-missing/install-button.js b/packages/block-directory/src/plugins/get-install-missing/install-button.js
index 2dc01184bdeb4a..075fed360c14c8 100644
--- a/packages/block-directory/src/plugins/get-install-missing/install-button.js
+++ b/packages/block-directory/src/plugins/get-install-missing/install-button.js
@@ -42,6 +42,7 @@ export default function InstallButton( { attributes, block, clientId } ) {
}
} )
}
+ __experimentalIsFocusable
disabled={ isInstallingBlock }
isBusy={ isInstallingBlock }
variant="primary"
diff --git a/packages/block-editor/src/components/button-block-appender/index.js b/packages/block-editor/src/components/button-block-appender/index.js
index 974f48e61bc287..cd1289c897824c 100644
--- a/packages/block-editor/src/components/button-block-appender/index.js
+++ b/packages/block-editor/src/components/button-block-appender/index.js
@@ -60,6 +60,8 @@ function ButtonBlockAppender(
onClick={ onToggle }
aria-haspopup={ isToggleButton ? 'true' : undefined }
aria-expanded={ isToggleButton ? isOpen : undefined }
+ // Disable reason: There shouldn't be a case where this button is disabled but not visually hidden.
+ // eslint-disable-next-line no-restricted-syntax
disabled={ disabled }
label={ label }
>
diff --git a/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js b/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js
index 6b9e694c1cdf8f..8db23267eee8f4 100644
--- a/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js
+++ b/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js
@@ -8,7 +8,7 @@ import {
parse,
} from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
-import { useCallback } from '@wordpress/element';
+import { useCallback, useMemo } from '@wordpress/element';
/**
* Internal dependencies
@@ -25,13 +25,18 @@ import { withRootClientIdOptionKey } from '../../../store/utils';
* @return {Array} Returns the block types state. (block types, categories, collections, onSelect handler)
*/
const useBlockTypesState = ( rootClientId, onInsert, isQuick ) => {
+ const options = useMemo(
+ () => ( { [ withRootClientIdOptionKey ]: ! isQuick } ),
+ [ isQuick ]
+ );
const [ items ] = useSelect(
( select ) => [
- select( blockEditorStore ).getInserterItems( rootClientId, {
- [ withRootClientIdOptionKey ]: ! isQuick,
- } ),
+ select( blockEditorStore ).getInserterItems(
+ rootClientId,
+ options
+ ),
],
- [ rootClientId, isQuick ]
+ [ rootClientId, options ]
);
const [ categories, collections ] = useSelect( ( select ) => {
diff --git a/packages/block-editor/src/components/link-control/link-preview.js b/packages/block-editor/src/components/link-control/link-preview.js
index 3a9ced64d9d614..44751351d30f29 100644
--- a/packages/block-editor/src/components/link-control/link-preview.js
+++ b/packages/block-editor/src/components/link-control/link-preview.js
@@ -166,6 +166,7 @@ export default function LinkPreview( {
isEmptyURL || showIconLabels ? '' : ': ' + value.url
) }
ref={ ref }
+ __experimentalIsFocusable
disabled={ isEmptyURL }
size="compact"
/>
diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js
index 238b2d6b3acc7c..78730463389e26 100644
--- a/packages/block-editor/src/components/link-control/search-input.js
+++ b/packages/block-editor/src/components/link-control/search-input.js
@@ -125,7 +125,7 @@ const LinkControlSearchInput = forwardRef(
className={ className }
value={ value }
onChange={ onInputChange }
- placeholder={ placeholder ?? __( 'Search or type url' ) }
+ placeholder={ placeholder ?? __( 'Search or type URL' ) }
__experimentalRenderSuggestions={
showSuggestions ? handleRenderSuggestions : null
}
diff --git a/packages/block-editor/src/components/provider/use-block-sync.js b/packages/block-editor/src/components/provider/use-block-sync.js
index 300c108a70cf1a..4e9cc9784554f5 100644
--- a/packages/block-editor/src/components/provider/use-block-sync.js
+++ b/packages/block-editor/src/components/provider/use-block-sync.js
@@ -9,7 +9,6 @@ import { cloneBlock } from '@wordpress/blocks';
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
-import { undoIgnoreBlocks } from '../../store/undo-ignore';
const noop = () => {};
@@ -274,10 +273,6 @@ export default function useBlockSync( {
const updateParent = isPersistent
? onChangeRef.current
: onInputRef.current;
- const undoIgnore = undoIgnoreBlocks.has( blocks );
- if ( undoIgnore ) {
- undoIgnoreBlocks.delete( blocks );
- }
updateParent( blocks, {
selection: {
selectionStart: getSelectionStart(),
@@ -285,7 +280,6 @@ export default function useBlockSync( {
initialPosition:
getSelectedBlocksInitialCaretPosition(),
},
- undoIgnore,
} );
}
previousAreBlocksDifferent = areBlocksDifferent;
diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js
index db2c615dd5d6ca..d44fa3e69f86ad 100644
--- a/packages/block-editor/src/store/actions.js
+++ b/packages/block-editor/src/store/actions.js
@@ -2010,7 +2010,7 @@ export function __unstableSetTemporarilyEditingAsBlocks(
* per_page: 'page_size',
* search: 'q',
* };
- * const url = new URL( 'https://api.openverse.engineering/v1/images/' );
+ * const url = new URL( 'https://api.openverse.org/v1/images/' );
* Object.entries( finalQuery ).forEach( ( [ key, value ] ) => {
* const queryKey = mapFromInserterMediaRequest[ key ] || key;
* url.searchParams.set( queryKey, value );
diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js
index 28a7b1da98f73f..5e4e0c7a222b0b 100644
--- a/packages/block-editor/src/store/private-actions.js
+++ b/packages/block-editor/src/store/private-actions.js
@@ -6,7 +6,6 @@ import { Platform } from '@wordpress/element';
/**
* Internal dependencies
*/
-import { undoIgnoreBlocks } from './undo-ignore';
import { store as blockEditorStore } from './index';
import { unlock } from '../lock-unlock';
@@ -292,34 +291,6 @@ export function deleteStyleOverride( id ) {
};
}
-/**
- * A higher-order action that mark every change inside a callback as "non-persistent"
- * and ignore pushing to the undo history stack. It's primarily used for synchronized
- * derived updates from the block editor without affecting the undo history.
- *
- * @param {() => void} callback The synchronous callback to derive updates.
- */
-export function syncDerivedUpdates( callback ) {
- return ( { dispatch, select, registry } ) => {
- registry.batch( () => {
- // Mark every change in the `callback` as non-persistent.
- dispatch( {
- type: 'SET_EXPLICIT_PERSISTENT',
- isPersistentChange: false,
- } );
- callback();
- dispatch( {
- type: 'SET_EXPLICIT_PERSISTENT',
- isPersistentChange: undefined,
- } );
-
- // Ignore pushing undo stack for the updated blocks.
- const updatedBlocks = select.getBlocks();
- undoIgnoreBlocks.add( updatedBlocks );
- } );
- };
-}
-
/**
* Action that sets the element that had focus when focus leaves the editor canvas.
*
diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js
index bf7b5125a770e6..bf98521dfe9b65 100644
--- a/packages/block-editor/src/store/selectors.js
+++ b/packages/block-editor/src/store/selectors.js
@@ -76,6 +76,8 @@ const EMPTY_ARRAY = [];
*/
const EMPTY_SET = new Set();
+const EMPTY_OBJECT = {};
+
/**
* Returns a block's name given its client ID, or null if no block exists with
* the client ID.
@@ -1996,7 +1998,7 @@ const buildBlockTypeItem =
*/
export const getInserterItems = createRegistrySelector( ( select ) =>
createSelector(
- ( state, rootClientId = null, options = {} ) => {
+ ( state, rootClientId = null, options = EMPTY_OBJECT ) => {
const buildReusableBlockInserterItem = ( reusableBlock ) => {
const icon = ! reusableBlock.wp_pattern_sync_status
? {
diff --git a/packages/block-editor/src/store/undo-ignore.js b/packages/block-editor/src/store/undo-ignore.js
deleted file mode 100644
index f0a64428ea7c26..00000000000000
--- a/packages/block-editor/src/store/undo-ignore.js
+++ /dev/null
@@ -1,4 +0,0 @@
-// Keep track of the blocks that should not be pushing an additional
-// undo stack when editing the entity.
-// See the implementation of `syncDerivedUpdates` and `useBlockSync`.
-export const undoIgnoreBlocks = new WeakSet();
diff --git a/packages/block-library/src/gallery/v1/gallery-image.js b/packages/block-library/src/gallery/v1/gallery-image.js
index 368d5da55c4ac9..5384944b2335d9 100644
--- a/packages/block-library/src/gallery/v1/gallery-image.js
+++ b/packages/block-library/src/gallery/v1/gallery-image.js
@@ -222,6 +222,8 @@ class GalleryImage extends Component {
onClick={ isFirstItem ? undefined : onMoveBackward }
label={ __( 'Move image backward' ) }
aria-disabled={ isFirstItem }
+ // Disable reason: Truly disable when image is not selected.
+ // eslint-disable-next-line no-restricted-syntax
disabled={ ! isSelected }
/>
@@ -237,12 +241,16 @@ class GalleryImage extends Component {
icon={ edit }
onClick={ this.onEdit }
label={ __( 'Replace image' ) }
+ // Disable reason: Truly disable when image is not selected.
+ // eslint-disable-next-line no-restricted-syntax
disabled={ ! isSelected }
/>
diff --git a/packages/block-library/src/missing/edit.native.js b/packages/block-library/src/missing/edit.native.js
index 7432f5fae0f12f..950d9afaaebf39 100644
--- a/packages/block-library/src/missing/edit.native.js
+++ b/packages/block-library/src/missing/edit.native.js
@@ -1,12 +1,7 @@
/**
* External dependencies
*/
-import {
- View,
- Text,
- TouchableWithoutFeedback,
- TouchableOpacity,
-} from 'react-native';
+import { View, Text, TouchableOpacity } from 'react-native';
/**
* WordPress dependencies
@@ -25,6 +20,7 @@ import {
store as blockEditorStore,
} from '@wordpress/block-editor';
import { store as noticesStore } from '@wordpress/notices';
+import { requestUnsupportedBlockFallback } from '@wordpress/react-native-bridge';
/**
* Internal dependencies
@@ -48,11 +44,38 @@ export class UnsupportedBlockEdit extends Component {
}
toggleSheet() {
+ const { attributes, block, clientId } = this.props;
+ const { originalName } = attributes;
+ const title = this.getTitle();
+ const blockContent = serialize( block ? [ block ] : [] );
+
+ if ( this.canEditUnsupportedBlock() ) {
+ requestUnsupportedBlockFallback(
+ blockContent,
+ clientId,
+ originalName,
+ title
+ );
+ return;
+ }
+
this.setState( {
showHelp: ! this.state.showHelp,
} );
}
+ canEditUnsupportedBlock() {
+ const {
+ canEnableUnsupportedBlockEditor,
+ isUnsupportedBlockEditorSupported,
+ } = this.props;
+
+ return (
+ ! canEnableUnsupportedBlockEditor &&
+ isUnsupportedBlockEditorSupported
+ );
+ }
+
closeSheet() {
this.setState( {
showHelp: false,
@@ -186,7 +209,11 @@ export class UnsupportedBlockEdit extends Component {
);
const subtitle = (
- { __( 'Unsupported' ) }
+
+ { this.canEditUnsupportedBlock()
+ ? __( 'Tap to edit' )
+ : __( 'Unsupported' ) }
+
);
const icon = blockType
@@ -198,8 +225,8 @@ export class UnsupportedBlockEdit extends Component {
);
const iconClassName = 'unsupported-icon' + '-' + preferredColorScheme;
return (
-
- { this.renderHelpIcon() }
+ { ! this.canEditUnsupportedBlock() &&
+ this.renderHelpIcon() }
-
+
);
}
}
diff --git a/packages/block-library/src/missing/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/missing/test/__snapshots__/edit.native.js.snap
index 245410a5c5d570..2ac371120be4bd 100644
--- a/packages/block-library/src/missing/test/__snapshots__/edit.native.js.snap
+++ b/packages/block-library/src/missing/test/__snapshots__/edit.native.js.snap
@@ -9,12 +9,21 @@ exports[`Missing block renders without crashing 1`] = `
{
"busy": undefined,
"checked": undefined,
- "disabled": true,
+ "disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
+ accessibilityValue={
+ {
+ "max": undefined,
+ "min": undefined,
+ "now": undefined,
+ "text": undefined,
+ }
+ }
accessible={true}
+ collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
@@ -23,72 +32,79 @@ exports[`Missing block renders without crashing 1`] = `
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
+ style={
+ {
+ "opacity": 1,
+ }
+ }
>
-
+
-
-
-
-
+
+
+
+
+
+ missing/block/title
+
+
- missing/block/title
+ Unsupported
-
- Unsupported
-
`;
diff --git a/packages/block-library/src/page-list/convert-to-links-modal.js b/packages/block-library/src/page-list/convert-to-links-modal.js
index 33ee22045b8241..12277bee01faf1 100644
--- a/packages/block-library/src/page-list/convert-to-links-modal.js
+++ b/packages/block-library/src/page-list/convert-to-links-modal.js
@@ -36,6 +36,7 @@ export function ConvertToLinksModal( { onClick, onClose, disabled } ) {
) : (
changePage( 1 ) }
+ // Disable reason: Would not cause confusion, and allows quicker access to a relevant nav button.
+ // eslint-disable-next-line no-restricted-syntax
disabled={ disabled || currentPage === 1 }
aria-label={ __( 'First page' ) }
>
@@ -54,6 +56,8 @@ export default function Pagination( {
changePage( currentPage - 1 ) }
+ // Disable reason: Would not cause confusion, and allows quicker access to a relevant nav button.
+ // eslint-disable-next-line no-restricted-syntax
disabled={ disabled || currentPage === 1 }
aria-label={ __( 'Previous page' ) }
>
@@ -72,6 +76,8 @@ export default function Pagination( {
changePage( currentPage + 1 ) }
+ // Disable reason: Would not cause confusion, and allows quicker access to a relevant nav button.
+ // eslint-disable-next-line no-restricted-syntax
disabled={ disabled || currentPage === numPages }
aria-label={ __( 'Next page' ) }
>
@@ -80,6 +86,8 @@ export default function Pagination( {
changePage( numPages ) }
+ // Disable reason: Would not cause confusion, and allows quicker access to a relevant nav button.
+ // eslint-disable-next-line no-restricted-syntax
disabled={ disabled || currentPage === numPages }
aria-label={ __( 'Last page' ) }
>
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/rename-modal.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/rename-modal.js
index a2281804bcb728..d290a40516bf0b 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/rename-modal.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/rename-modal.js
@@ -43,6 +43,7 @@ export default function RenameModal( { menuTitle, onClose, onSave } ) {
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen/style.scss
index bf6b79d151d595..7691f9ba2cdb0b 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen/style.scss
+++ b/packages/edit-site/src/components/sidebar-navigation-screen/style.scss
@@ -67,8 +67,8 @@
.edit-site-sidebar-navigation-screen__title {
flex-grow: 1;
- padding: $grid-unit-15 * 0.5 0 0 0;
overflow-wrap: break-word;
+ padding: $grid-unit-05 * 0.5 0 0 0;
}
.edit-site-sidebar-navigation-screen__actions {
diff --git a/packages/edit-site/src/hooks/push-changes-to-global-styles/index.js b/packages/edit-site/src/hooks/push-changes-to-global-styles/index.js
index a3ccc3e7c3234e..9e876d39d73407 100644
--- a/packages/edit-site/src/hooks/push-changes-to-global-styles/index.js
+++ b/packages/edit-site/src/hooks/push-changes-to-global-styles/index.js
@@ -339,6 +339,7 @@ function PushChangesToGlobalStylesControl( {
diff --git a/packages/editor/README.md b/packages/editor/README.md
index 6e887b6c0b82e6..d4161f9bac6266 100644
--- a/packages/editor/README.md
+++ b/packages/editor/README.md
@@ -1592,7 +1592,11 @@ Undocumented declaration.
### WordCount
-Undocumented declaration.
+Renders the word count of the post content.
+
+_Returns_
+
+- `JSX.Element|null`: The rendered WordCount component.
### WritingFlow
diff --git a/packages/editor/src/components/collapsible-block-toolbar/style.scss b/packages/editor/src/components/collapsible-block-toolbar/style.scss
index 701d6fa7c5ecc2..80d25da1a3e28e 100644
--- a/packages/editor/src/components/collapsible-block-toolbar/style.scss
+++ b/packages/editor/src/components/collapsible-block-toolbar/style.scss
@@ -64,8 +64,9 @@
// Move up a little to prevent the toolbar shift when focus is on the vertical movers.
@include break-small() {
&:not(.is-horizontal) .block-editor-block-mover__move-button-container {
+ height: $grid-unit-50;
position: relative;
- top: -10px;
+ top: -5px; // Should be -4px, but that causes scrolling when focus lands on the movers, in a 60px header.
}
}
}
diff --git a/packages/editor/src/components/editor-interface/index.js b/packages/editor/src/components/editor-interface/index.js
index b78e3caa0b57cb..386214189cfdac 100644
--- a/packages/editor/src/components/editor-interface/index.js
+++ b/packages/editor/src/components/editor-interface/index.js
@@ -160,7 +160,7 @@ export default function EditorInterface( {
{ ( [ editorCanvasView ] ) =>
- ! isPreviewMode && editorCanvasView ? (
+ editorCanvasView ? (
editorCanvasView
) : (
<>
diff --git a/packages/editor/src/components/media-categories/index.js b/packages/editor/src/components/media-categories/index.js
index 345f5c5c86e2c2..be1b2bdc2d6728 100644
--- a/packages/editor/src/components/media-categories/index.js
+++ b/packages/editor/src/components/media-categories/index.js
@@ -191,9 +191,7 @@ const inserterMediaCategories = [
per_page: 'page_size',
search: 'q',
};
- const url = new URL(
- 'https://api.openverse.engineering/v1/images/'
- );
+ const url = new URL( 'https://api.openverse.org/v1/images/' );
Object.entries( finalQuery ).forEach( ( [ key, value ] ) => {
const queryKey = mapFromInserterMediaRequest[ key ] || key;
url.searchParams.set( queryKey, value );
diff --git a/packages/editor/src/components/post-panel-row/style.scss b/packages/editor/src/components/post-panel-row/style.scss
index baa7d7dd98977d..024394c3aaf331 100644
--- a/packages/editor/src/components/post-panel-row/style.scss
+++ b/packages/editor/src/components/post-panel-row/style.scss
@@ -25,6 +25,7 @@
.components-button {
max-width: 100%;
text-align: left;
+ text-wrap: balance; // Fallback for Safari.
text-wrap: pretty;
height: auto;
min-height: $button-size-compact;
diff --git a/packages/editor/src/components/post-preview-button/index.js b/packages/editor/src/components/post-preview-button/index.js
index 110de5858af382..e517ac0e8a0fbc 100644
--- a/packages/editor/src/components/post-preview-button/index.js
+++ b/packages/editor/src/components/post-preview-button/index.js
@@ -183,6 +183,7 @@ export default function PostPreviewButton( {
className={ className || 'editor-post-preview' }
href={ href }
target={ targetId }
+ __experimentalIsFocusable
disabled={ ! isSaveable }
onClick={ openPreviewWindow }
role={ role }
diff --git a/packages/editor/src/components/post-preview-button/test/index.js b/packages/editor/src/components/post-preview-button/test/index.js
index e34c05caa178bd..bb51f302edf50e 100644
--- a/packages/editor/src/components/post-preview-button/test/index.js
+++ b/packages/editor/src/components/post-preview-button/test/index.js
@@ -139,12 +139,16 @@ describe( 'PostPreviewButton', () => {
).toBeInTheDocument();
} );
- it( 'should be disabled if post is not saveable.', () => {
+ it( 'should be accessibly disabled if post is not saveable.', () => {
mockUseSelect( { isEditedPostSaveable: () => false } );
render( );
- expect( screen.getByRole( 'button' ) ).toBeDisabled();
+ expect( screen.getByRole( 'button' ) ).toBeEnabled();
+ expect( screen.getByRole( 'button' ) ).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
} );
it( 'should not be disabled if post is saveable.', () => {
@@ -153,6 +157,10 @@ describe( 'PostPreviewButton', () => {
render( );
expect( screen.getByRole( 'button' ) ).toBeEnabled();
+ expect( screen.getByRole( 'button' ) ).not.toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
} );
it( 'should set `href` to edited post preview link if specified.', () => {
diff --git a/packages/editor/src/components/post-publish-panel/index.js b/packages/editor/src/components/post-publish-panel/index.js
index 4d59133966759a..31e838575c0871 100644
--- a/packages/editor/src/components/post-publish-panel/index.js
+++ b/packages/editor/src/components/post-publish-panel/index.js
@@ -93,6 +93,7 @@ export class PostPublishPanel extends Component {
select( editorStore ).getEditedPostAttribute( 'content' ),
diff --git a/packages/interactivity/src/store.ts b/packages/interactivity/src/store.ts
index f6366283d2d6a9..281a6c266021e1 100644
--- a/packages/interactivity/src/store.ts
+++ b/packages/interactivity/src/store.ts
@@ -321,7 +321,9 @@ export function store(
export const parseInitialData = ( dom = document ) => {
const jsonDataScriptTag =
// Preferred Script Module data passing form
- dom.getElementById( 'wp-scriptmodule-data_@wordpress/interactivity' ) ??
+ dom.getElementById(
+ 'wp-script-module-data-@wordpress/interactivity'
+ ) ??
// Legacy form
dom.getElementById( 'wp-interactivity-data' );
if ( jsonDataScriptTag?.textContent ) {
diff --git a/packages/list-reusable-blocks/src/components/import-form/index.js b/packages/list-reusable-blocks/src/components/import-form/index.js
index 1f5e62f9115b36..aa2f3133b7a6ef 100644
--- a/packages/list-reusable-blocks/src/components/import-form/index.js
+++ b/packages/list-reusable-blocks/src/components/import-form/index.js
@@ -86,6 +86,7 @@ function ImportForm( { instanceId, onUpload } ) {
* {
+ display: none;
+}
+
+/* Show only the interface-pinned-items container */
+.editor-header__settings > .interface-pinned-items {
+ display: block;
+}
+
+.components-button.editor-post-publish-panel__toggle.is-primary{
+ display: none;
+}
+
/* Remove default block appender at the end of the post */
-.block-list-appender {
+.block-list-appender, .block-list-appender__toggle,
+.block-editor-inserter, .components-autocomplete__popover {
display: none;
}
@@ -18,6 +38,11 @@
display: none;
}
+/* Remove block inserter popover */
+.block-editor-block-popover {
+ display: none;
+}
+
/* Right align post header children as we will only display one child */
.edit-post-header {
justify-content: flex-end;
diff --git a/storybook/stories/playground/with-undo-redo/index.js b/storybook/stories/playground/with-undo-redo/index.js
index 8bef2d184f8c59..c2b2781cd31e4f 100644
--- a/storybook/stories/playground/with-undo-redo/index.js
+++ b/storybook/stories/playground/with-undo-redo/index.js
@@ -49,12 +49,14 @@ export default function EditorWithUndoRedo() {
diff --git a/test/e2e/specs/editor/blocks/links.spec.js b/test/e2e/specs/editor/blocks/links.spec.js
index 5686eaed0c83e5..02400fe9c9dd8a 100644
--- a/test/e2e/specs/editor/blocks/links.spec.js
+++ b/test/e2e/specs/editor/blocks/links.spec.js
@@ -235,7 +235,7 @@ test.describe( 'Links', () => {
// Change the URL.
// getByPlaceholder required in order to handle Link Control component
// managing focus onto other inputs within the control.
- await page.getByPlaceholder( 'Search or type url' ).fill( '' );
+ await page.getByPlaceholder( 'Search or type URL' ).fill( '' );
await page.keyboard.type( '/handbook' );
// Submit the link.
@@ -349,7 +349,7 @@ test.describe( 'Links', () => {
// Change the URL.
// getByPlaceholder required in order to handle Link Control component
// managing focus onto other inputs within the control.
- await page.getByPlaceholder( 'Search or type url' ).fill( '' );
+ await page.getByPlaceholder( 'Search or type URL' ).fill( '' );
await page.keyboard.type( '/handbook' );
// Submit the link.
@@ -679,7 +679,7 @@ test.describe( 'Links', () => {
// Change the URL.
// Note: getByPlaceholder required in order to handle Link Control component
// managing focus onto other inputs within the control.
- await linkPopover.getByPlaceholder( 'Search or type url' ).fill( '' );
+ await linkPopover.getByPlaceholder( 'Search or type URL' ).fill( '' );
await page.keyboard.type( 'wordpress.org' );
// Save the link.
diff --git a/test/e2e/specs/editor/various/block-bindings.spec.js b/test/e2e/specs/editor/various/block-bindings.spec.js
index 87e5b2f2e10b11..be5c7594b46dad 100644
--- a/test/e2e/specs/editor/various/block-bindings.spec.js
+++ b/test/e2e/specs/editor/various/block-bindings.spec.js
@@ -2044,7 +2044,7 @@ test.describe( 'Block bindings', () => {
.getByRole( 'button', { name: 'Edit link', exact: true } )
.click();
await page
- .getByPlaceholder( 'Search or type url' )
+ .getByPlaceholder( 'Search or type URL' )
.fill( '#url-custom-field-modified' );
await pageUtils.pressKeys( 'Enter' );
@@ -2103,7 +2103,7 @@ test.describe( 'Block bindings', () => {
.getByRole( 'button', { name: 'Edit link', exact: true } )
.click();
await page
- .getByPlaceholder( 'Search or type url' )
+ .getByPlaceholder( 'Search or type URL' )
.fill( imageCustomFieldSrc );
await pageUtils.pressKeys( 'Enter' );
diff --git a/test/native/integration-test-helpers/rich-text-paste.js b/test/native/integration-test-helpers/rich-text-paste.js
index d2c01ed2fb5a72..6d447181e0b8d2 100644
--- a/test/native/integration-test-helpers/rich-text-paste.js
+++ b/test/native/integration-test-helpers/rich-text-paste.js
@@ -6,19 +6,20 @@ import { fireEvent } from '@testing-library/react-native';
/**
* Paste content into a RichText component.
*
- * @param {import('react-test-renderer').ReactTestInstance} richText RichText test instance.
- * @param {Object} content Content to paste.
- * @param {string} content.text Text format of the content.
- * @param {string} [content.html] HTML format of the content. If not provided, text format will be used.
+ * @param {import('react-test-renderer').ReactTestInstance} richText RichText test instance.
+ * @param {Object} content Content to paste.
+ * @param {string} content.text Text format of the content.
+ * @param {string} [content.html] HTML format of the content. If not provided, text format will be used.
+ * @param {string} [content.files] Files array to add to the editor.
*/
-export const pasteIntoRichText = ( richText, { text, html } ) => {
+export const pasteIntoRichText = ( richText, { text, html, files = [] } ) => {
fireEvent( richText, 'focus' );
fireEvent( richText, 'paste', {
preventDefault: jest.fn(),
nativeEvent: {
eventCount: 1,
target: undefined,
- files: [],
+ files,
pastedHtml: html || text,
pastedText: text,
},