diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md
index 628225f536e45b..2bf81ebf2cc969 100644
--- a/docs/reference-guides/core-blocks.md
+++ b/docs/reference-guides/core-blocks.md
@@ -38,7 +38,7 @@ Add a user’s avatar. ([Source](https://github.com/WordPress/gutenberg/tree/tru
## Pattern
-Create and save content to reuse across your site. Update the block, and the changes apply everywhere it’s used. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/block))
+Create and save content to reuse across your site. Update the pattern, and the changes apply everywhere it’s used. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/block))
- **Name:** core/block
- **Category:** reusable
diff --git a/lib/compat/wordpress-6.3/footnotes.php b/lib/compat/wordpress-6.3/footnotes.php
new file mode 100644
index 00000000000000..45f4c74b05e37b
--- /dev/null
+++ b/lib/compat/wordpress-6.3/footnotes.php
@@ -0,0 +1,25 @@
+\s*\d+ \s*_';
+ return preg_replace( $footnote_pattern, '', $content );
+}
+
+add_filter( 'the_content', 'gutenberg_trim_footnotes' );
diff --git a/lib/load.php b/lib/load.php
index 8b76586030c35d..cc3f46cb7837fe 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -57,6 +57,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require_once __DIR__ . '/compat/wordpress-6.3/link-template.php';
require_once __DIR__ . '/compat/wordpress-6.3/block-patterns.php';
require_once __DIR__ . '/compat/wordpress-6.3/class-gutenberg-rest-blocks-controller.php';
+ require_once __DIR__ . '/compat/wordpress-6.3/footnotes.php';
// Experimental.
if ( ! class_exists( 'WP_Rest_Customizer_Nonces' ) ) {
diff --git a/packages/block-editor/src/components/block-draggable/index.js b/packages/block-editor/src/components/block-draggable/index.js
index 1e4048c90ab618..0b8f3c2d87f520 100644
--- a/packages/block-editor/src/components/block-draggable/index.js
+++ b/packages/block-editor/src/components/block-draggable/index.js
@@ -22,16 +22,25 @@ const BlockDraggable = ( {
} ) => {
const { srcRootClientId, isDraggable, icon } = useSelect(
( select ) => {
- const { canMoveBlocks, getBlockRootClientId, getBlockName } =
- select( blockEditorStore );
- const { getBlockType } = select( blocksStore );
+ const {
+ canMoveBlocks,
+ getBlockRootClientId,
+ getBlockName,
+ getBlockAttributes,
+ } = select( blockEditorStore );
+ const { getBlockType, getActiveBlockVariation } =
+ select( blocksStore );
const rootClientId = getBlockRootClientId( clientIds[ 0 ] );
const blockName = getBlockName( clientIds[ 0 ] );
+ const variation = getActiveBlockVariation(
+ blockName,
+ getBlockAttributes( clientIds[ 0 ] )
+ );
return {
srcRootClientId: rootClientId,
isDraggable: canMoveBlocks( clientIds, rootClientId ),
- icon: getBlockType( blockName )?.icon,
+ icon: variation?.icon || getBlockType( blockName )?.icon,
};
},
[ clientIds ]
diff --git a/packages/block-editor/src/components/inserter/reusable-block-rename-hint.js b/packages/block-editor/src/components/inserter/reusable-block-rename-hint.js
index 09861d9b97f1c9..d4702eb1372831 100644
--- a/packages/block-editor/src/components/inserter/reusable-block-rename-hint.js
+++ b/packages/block-editor/src/components/inserter/reusable-block-rename-hint.js
@@ -29,7 +29,7 @@ export default function ReusableBlocksRenameHint() {
{ __(
- 'Reusable blocks are now called patterns. A synced pattern will behave in exactly the same way as a reusable block.'
+ 'Reusable blocks are now synced patterns. A synced pattern will behave in exactly the same way as a reusable block.'
) }
{
if ( ! searchTerm ) {
@@ -40,27 +34,15 @@ export const LinkControlSearchCreate = ( {
}
return (
-
-
-
-
-
- { text }
-
-
-
+ { text }
+
);
};
diff --git a/packages/block-editor/src/components/link-control/search-item.js b/packages/block-editor/src/components/link-control/search-item.js
index 250b28596f4f34..976bb4420cb0cf 100644
--- a/packages/block-editor/src/components/link-control/search-item.js
+++ b/packages/block-editor/src/components/link-control/search-item.js
@@ -1,14 +1,8 @@
-/**
- * External dependencies
- */
-import classnames from 'classnames';
-
/**
* WordPress dependencies
*/
-import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url';
import { __ } from '@wordpress/i18n';
-import { Button, TextHighlight } from '@wordpress/components';
+import { MenuItem, TextHighlight } from '@wordpress/components';
import {
Icon,
globe,
@@ -19,6 +13,7 @@ import {
file,
} from '@wordpress/icons';
import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
+import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url';
const ICONS_MAP = {
post: postList,
@@ -52,50 +47,33 @@ function SearchItemIcon( { isURL, suggestion } ) {
export const LinkControlSearchItem = ( {
itemProps,
suggestion,
- isSelected = false,
+ searchTerm,
onClick,
isURL = false,
- searchTerm = '',
shouldShowType = false,
} ) => {
+ const info = isURL
+ ? __( 'Press ENTER to add this link' )
+ : filterURLForDisplay( safeDecodeURI( suggestion?.url ) );
+
return (
-
+ }
onClick={ onClick }
- className={ classnames( 'block-editor-link-control__search-item', {
- 'is-selected': isSelected,
- 'is-url': isURL,
- 'is-entity': ! isURL,
- } ) }
+ shortcut={ shouldShowType && getVisualTypeName( suggestion ) }
+ className="block-editor-link-control__search-item"
>
-
-
-
-
-
-
-
- { ! isURL &&
- ( filterURLForDisplay(
- safeDecodeURI( suggestion.url )
- ) ||
- '' ) }
- { isURL && __( 'Press ENTER to add this link' ) }
-
-
- { shouldShowType && suggestion.type && (
-
- { getVisualTypeName( suggestion ) }
-
- ) }
-
+
+
);
};
diff --git a/packages/block-editor/src/components/link-control/search-results.js b/packages/block-editor/src/components/link-control/search-results.js
index 9d7ee7ca41abd9..71e258c769bf17 100644
--- a/packages/block-editor/src/components/link-control/search-results.js
+++ b/packages/block-editor/src/components/link-control/search-results.js
@@ -2,7 +2,7 @@
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
-import { VisuallyHidden } from '@wordpress/components';
+import { VisuallyHidden, MenuGroup } from '@wordpress/components';
/**
* External dependencies
@@ -72,59 +72,61 @@ export default function LinkControlSearchResults( {
className={ resultsListClasses }
aria-labelledby={ searchResultsLabelId }
>
- { suggestions.map( ( suggestion, index ) => {
- if (
- shouldShowCreateSuggestion &&
- CREATE_TYPE === suggestion.type
- ) {
+
+ { suggestions.map( ( suggestion, index ) => {
+ if (
+ shouldShowCreateSuggestion &&
+ CREATE_TYPE === suggestion.type
+ ) {
+ return (
+
+ handleSuggestionClick( suggestion )
+ }
+ // Intentionally only using `type` here as
+ // the constant is enough to uniquely
+ // identify the single "CREATE" suggestion.
+ key={ suggestion.type }
+ itemProps={ buildSuggestionItemProps(
+ suggestion,
+ index
+ ) }
+ isSelected={ index === selectedSuggestion }
+ />
+ );
+ }
+
+ // If we're not handling "Create" suggestions above then
+ // we don't want them in the main results so exit early.
+ if ( CREATE_TYPE === suggestion.type ) {
+ return null;
+ }
+
return (
-
- handleSuggestionClick( suggestion )
- }
- // Intentionally only using `type` here as
- // the constant is enough to uniquely
- // identify the single "CREATE" suggestion.
- key={ suggestion.type }
+ {
+ handleSuggestionClick( suggestion );
+ } }
isSelected={ index === selectedSuggestion }
+ isURL={ LINK_ENTRY_TYPES.includes(
+ suggestion.type
+ ) }
+ searchTerm={ currentInputValue }
+ shouldShowType={ shouldShowSuggestionsTypes }
+ isFrontPage={ suggestion?.isFrontPage }
/>
);
- }
-
- // If we're not handling "Create" suggestions above then
- // we don't want them in the main results so exit early.
- if ( CREATE_TYPE === suggestion.type ) {
- return null;
- }
-
- return (
- {
- handleSuggestionClick( suggestion );
- } }
- isSelected={ index === selectedSuggestion }
- isURL={ LINK_ENTRY_TYPES.includes(
- suggestion.type
- ) }
- searchTerm={ currentInputValue }
- shouldShowType={ shouldShowSuggestionsTypes }
- isFrontPage={ suggestion?.isFrontPage }
- />
- );
- } ) }
+ } ) }
+
);
diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss
index eb9769c2d299ba..0d93ff01bcc66a 100644
--- a/packages/block-editor/src/components/link-control/style.scss
+++ b/packages/block-editor/src/components/link-control/style.scss
@@ -41,6 +41,7 @@ $preview-image-height: 140px;
// Provides positioning context for reset button. Without this then when an
// error notice is displayed the input's reset button is incorrectly positioned.
.block-editor-link-control__search-input-wrapper {
+ margin-bottom: $grid-unit-10;
position: relative;
}
@@ -87,36 +88,9 @@ $preview-image-height: 140px;
order: 20;
}
-.block-editor-link-control__search-results-wrapper {
- position: relative;
-
- &::before,
- &::after {
- content: "";
- position: absolute;
- left: -1px;
- right: $grid-unit-20; // avoid overlaying scrollbars
- display: block;
- pointer-events: none;
- z-index: 100;
- }
-
- &::before {
- height: $grid-unit-20 * 0.5;
- top: 0;
- bottom: auto;
- }
-
- &::after {
- height: $grid-unit-20;
- bottom: 0;
- top: auto;
- }
-}
-
.block-editor-link-control__search-results {
- margin: 0;
- padding: $grid-unit-20 * 0.5 $grid-unit-20 $grid-unit-20 * 0.5;
+ margin-top: -$grid-unit-20;
+ padding: $grid-unit-10;
max-height: 200px;
overflow-y: auto; // allow results list to scroll
@@ -126,39 +100,35 @@ $preview-image-height: 140px;
}
.block-editor-link-control__search-item {
- position: relative;
- display: flex;
- align-items: flex-start; // when link text is very long it is important this indicator remains visible and thus should be aligned top.
- font-size: $default-font-size;
- cursor: pointer;
- background: $white;
- width: 100%;
- border: none;
- text-align: left;
- padding: $grid-unit-15 $grid-unit-20;
- border-radius: 2px;
- height: auto;
- &:hover,
- &:focus {
- background-color: $gray-100;
+ &.components-button.components-menu-item__button {
+ height: auto;
+ text-align: left;
+ }
- .block-editor-link-control__search-item-type {
- background: $white;
+ .components-menu-item__item {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ // Inline block required to preserve white space
+ // between `` elements and text nodes.
+ display: inline-block;
+ width: 100%;
+
+ mark {
+ font-weight: 600;
+ color: inherit;
+ background-color: transparent;
}
}
- // The added specificity is needed to override.
- &:focus:not(:disabled) {
- box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color) inset;
+ .components-menu-item__shortcut {
+ color: $gray-700;
+ text-transform: capitalize;
+ white-space: nowrap; // tags shouldn't go over two lines.
}
- &.is-selected {
+ &[aria-selected] {
background: $gray-100;
-
- .block-editor-link-control__search-item-type {
- background: $white;
- }
}
&.is-current {
@@ -198,7 +168,6 @@ $preview-image-height: 140px;
.block-editor-link-control__search-item-icon {
position: relative;
- top: 0.2em;
margin-right: $grid-unit-10;
max-height: 24px;
flex-shrink: 0;
@@ -217,18 +186,6 @@ $preview-image-height: 140px;
max-height: 32px;
}
- .block-editor-link-control__search-item-info,
- .block-editor-link-control__search-item-title {
- overflow: hidden;
- text-overflow: ellipsis;
-
- .components-external-link__icon {
- position: absolute;
- right: 0;
- margin-top: 0;
- }
- }
-
.block-editor-link-control__search-item-title {
display: block;
margin-bottom: 0.2em;
@@ -236,7 +193,7 @@ $preview-image-height: 140px;
position: relative;
mark {
- font-weight: 700;
+ font-weight: 600;
color: inherit;
background-color: transparent;
}
@@ -250,28 +207,6 @@ $preview-image-height: 140px;
}
}
- .block-editor-link-control__search-item-info {
- display: block;
- color: $gray-700;
- font-size: 0.9em;
- line-height: 1.3;
- }
-
- .block-editor-link-control__search-item-error-notice {
- font-style: italic;
- font-size: 1.1em;
- }
-
- .block-editor-link-control__search-item-type {
- display: block;
- padding: 3px 6px;
- margin-left: auto;
- font-size: 0.9em;
- background-color: $gray-100;
- border-radius: 2px;
- white-space: nowrap; // tags shouldn't go over two lines.
- }
-
.block-editor-link-control__search-item-description {
padding-top: 12px;
margin: 0;
@@ -411,11 +346,6 @@ $preview-image-height: 140px;
}
}
-// Specificity override
-.block-editor-link-control__search-results div[role="menu"] > .block-editor-link-control__search-item.block-editor-link-control__search-item {
- padding: 10px;
-}
-
.block-editor-link-control__drawer {
display: flex; // allow for ordering.
order: 30;
diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js
index 20b72363391ee1..82bb82a0cfc51d 100644
--- a/packages/block-editor/src/components/link-control/test/index.js
+++ b/packages/block-editor/src/components/link-control/test/index.js
@@ -478,16 +478,16 @@ describe( 'Searching for a link', () => {
// The fallback URL suggestion should not be shown when input is not URL-like.
expect(
searchResultElements[ searchResultElements.length - 1 ]
- ).not.toHaveTextContent( 'URL' );
+ ).not.toHaveTextContent( 'Press ENTER to add this link' );
}
);
it.each( [
- [ 'https://wordpress.org', 'URL' ],
- [ 'http://wordpress.org', 'URL' ],
- [ 'www.wordpress.org', 'URL' ],
- [ 'wordpress.org', 'URL' ],
- [ 'ftp://wordpress.org', 'URL' ],
+ [ 'https://wordpress.org', 'link' ],
+ [ 'http://wordpress.org', 'link' ],
+ [ 'www.wordpress.org', 'link' ],
+ [ 'wordpress.org', 'link' ],
+ [ 'ftp://wordpress.org', 'link' ],
[ 'mailto:hello@wordpress.org', 'mailto' ],
[ 'tel:123456789', 'tel' ],
[ '#internal', 'internal' ],
@@ -667,7 +667,6 @@ describe( 'Manual link entry', () => {
expect( searchResultElements ).toBeVisible();
expect( searchResultElements ).toHaveTextContent( searchTerm );
- expect( searchResultElements ).toHaveTextContent( 'URL' );
expect( searchResultElements ).toHaveTextContent(
'Press ENTER to add this link'
);
diff --git a/packages/block-editor/src/components/use-block-display-information/index.js b/packages/block-editor/src/components/use-block-display-information/index.js
index 87909cea45f637..1cff9da4bc04a9 100644
--- a/packages/block-editor/src/components/use-block-display-information/index.js
+++ b/packages/block-editor/src/components/use-block-display-information/index.js
@@ -67,8 +67,11 @@ export default function useBlockDisplayInformation( clientId ) {
return useSelect(
( select ) => {
if ( ! clientId ) return null;
- const { getBlockName, getBlockAttributes } =
- select( blockEditorStore );
+ const {
+ getBlockName,
+ getBlockAttributes,
+ __experimentalGetReusableBlockTitle,
+ } = select( blockEditorStore );
const { getBlockType, getActiveBlockVariation } =
select( blocksStore );
const blockName = getBlockName( clientId );
@@ -76,12 +79,16 @@ export default function useBlockDisplayInformation( clientId ) {
if ( ! blockType ) return null;
const attributes = getBlockAttributes( clientId );
const match = getActiveBlockVariation( blockName, attributes );
- const isSynced =
- isReusableBlock( blockType ) || isTemplatePart( blockType );
+ const isReusable = isReusableBlock( blockType );
+ const resusableTitle = isReusable
+ ? __experimentalGetReusableBlockTitle( attributes.ref )
+ : undefined;
+ const title = resusableTitle || blockType.title;
+ const isSynced = isReusable || isTemplatePart( blockType );
const positionLabel = getPositionTypeLabel( attributes );
const blockTypeInfo = {
isSynced,
- title: blockType.title,
+ title,
icon: blockType.icon,
description: blockType.description,
anchor: attributes?.anchor,
diff --git a/packages/block-editor/src/hooks/margin.js b/packages/block-editor/src/hooks/margin.js
index bdf5cabea6a9df..8f723b3f8c97de 100644
--- a/packages/block-editor/src/hooks/margin.js
+++ b/packages/block-editor/src/hooks/margin.js
@@ -23,7 +23,10 @@ export function MarginVisualizer( { clientId, attributes, forceShow } ) {
const margin = attributes?.style?.spacing?.margin;
useEffect( () => {
- if ( ! blockElement ) {
+ if (
+ ! blockElement ||
+ null === blockElement.ownerDocument.defaultView
+ ) {
return;
}
diff --git a/packages/block-editor/src/hooks/padding.js b/packages/block-editor/src/hooks/padding.js
index f451f2cb4262ea..b6e4e50e30f9cf 100644
--- a/packages/block-editor/src/hooks/padding.js
+++ b/packages/block-editor/src/hooks/padding.js
@@ -23,7 +23,10 @@ export function PaddingVisualizer( { clientId, attributes, forceShow } ) {
const padding = attributes?.style?.spacing?.padding;
useEffect( () => {
- if ( ! blockElement ) {
+ if (
+ ! blockElement ||
+ null === blockElement.ownerDocument.defaultView
+ ) {
return;
}
diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js
index 4cca99535a8e50..b6d030293c8154 100644
--- a/packages/block-editor/src/store/selectors.js
+++ b/packages/block-editor/src/store/selectors.js
@@ -2018,7 +2018,7 @@ export const getInserterItems = createSelector(
title: reusableBlock.title.raw,
icon,
category: 'reusable',
- keywords: [],
+ keywords: [ 'reusable' ],
isDisabled: false,
utility: 1, // Deprecated.
frecency,
diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js
index 270bceaad447fb..fc4db2c41f8a3f 100644
--- a/packages/block-editor/src/store/test/selectors.js
+++ b/packages/block-editor/src/store/test/selectors.js
@@ -3351,7 +3351,7 @@ describe( 'selectors', () => {
id: 'core/block/1',
initialAttributes: { ref: 1 },
isDisabled: false,
- keywords: [],
+ keywords: [ 'reusable' ],
name: 'core/block',
syncStatus: undefined,
title: 'Reusable Block 1',
diff --git a/packages/block-library/src/block/block.json b/packages/block-library/src/block/block.json
index 5846e7ead0c9b6..d6630177fc6853 100644
--- a/packages/block-library/src/block/block.json
+++ b/packages/block-library/src/block/block.json
@@ -4,7 +4,8 @@
"name": "core/block",
"title": "Pattern",
"category": "reusable",
- "description": "Create and save content to reuse across your site. Update the block, and the changes apply everywhere it’s used.",
+ "description": "Create and save content to reuse across your site. Update the pattern, and the changes apply everywhere it’s used.",
+ "keywords": [ "reusable" ],
"textdomain": "default",
"attributes": {
"ref": {
diff --git a/packages/block-library/src/cover/deprecated.js b/packages/block-library/src/cover/deprecated.js
index 7212af5c5cdc91..d1801a11ade9de 100644
--- a/packages/block-library/src/cover/deprecated.js
+++ b/packages/block-library/src/cover/deprecated.js
@@ -329,6 +329,7 @@ const v11 = {
);
},
+ migrate: migrateTag,
};
// Deprecation for blocks that renders fixed background as backgroud from the main block container.
@@ -465,6 +466,7 @@ const v10 = {
);
},
+ migrate: migrateTag,
};
// Deprecation for blocks with `minHeightUnit` set but no `minHeight`.
diff --git a/packages/block-library/src/footnotes/format.js b/packages/block-library/src/footnotes/format.js
index 40de6a132ea99d..eb700787d02eeb 100644
--- a/packages/block-library/src/footnotes/format.js
+++ b/packages/block-library/src/footnotes/format.js
@@ -40,24 +40,30 @@ export const format = {
} = useSelect( blockEditorStore );
const { selectionChange, insertBlock } =
useDispatch( blockEditorStore );
+
function onClick() {
registry.batch( () => {
- const id = createId();
- const newValue = insertObject(
- value,
- {
- type: formatName,
- attributes: {
- 'data-fn': id,
+ let id;
+ if ( isObjectActive ) {
+ const object = value.replacements[ value.start ];
+ id = object?.attributes?.[ 'data-fn' ];
+ } else {
+ id = createId();
+ const newValue = insertObject(
+ value,
+ {
+ type: formatName,
+ attributes: {
+ 'data-fn': id,
+ },
+ innerHTML: `* `,
},
- innerHTML: `* `,
- },
- value.end,
- value.end
- );
- newValue.start = newValue.end - 1;
-
- onChange( newValue );
+ value.end,
+ value.end
+ );
+ newValue.start = newValue.end - 1;
+ onChange( newValue );
+ }
// BFS search to find the first footnote block.
let fnBlock = null;
diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js
index 7ec990d5b7389c..87078999b0c9b0 100644
--- a/packages/block-library/src/navigation/edit/index.js
+++ b/packages/block-library/src/navigation/edit/index.js
@@ -196,7 +196,7 @@ function Navigation( {
convert: convertClassicMenu,
status: classicMenuConversionStatus,
error: classicMenuConversionError,
- } = useConvertClassicToBlockMenu( clientId );
+ } = useConvertClassicToBlockMenu( createNavigationMenu );
const isConvertingClassicMenu =
classicMenuConversionStatus === CLASSIC_MENU_CONVERSION_PENDING;
diff --git a/packages/block-library/src/navigation/edit/use-convert-classic-menu-to-block-menu.js b/packages/block-library/src/navigation/edit/use-convert-classic-menu-to-block-menu.js
index 70f9a6ff4bfeab..405663726cee89 100644
--- a/packages/block-library/src/navigation/edit/use-convert-classic-menu-to-block-menu.js
+++ b/packages/block-library/src/navigation/edit/use-convert-classic-menu-to-block-menu.js
@@ -9,7 +9,6 @@ import { __, sprintf } from '@wordpress/i18n';
/**
* Internal dependencies
*/
-import useCreateNavigationMenu from './use-create-navigation-menu';
import menuItemsToBlocks from '../menu-items-to-blocks';
export const CLASSIC_MENU_CONVERSION_SUCCESS = 'success';
@@ -21,15 +20,7 @@ export const CLASSIC_MENU_CONVERSION_IDLE = 'idle';
// do not import the same classic menu twice.
let classicMenuBeingConvertedId = null;
-function useConvertClassicToBlockMenu( clientId ) {
- /*
- * The wp_navigation post is created as a draft so the changes on the frontend and
- * the site editor are not permanent without a save interaction done by the user.
- */
- const { create: createNavigationMenu } = useCreateNavigationMenu(
- clientId,
- 'draft'
- );
+function useConvertClassicToBlockMenu( createNavigationMenu ) {
const registry = useRegistry();
const { editEntityRecord } = useDispatch( coreStore );
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index bdf9ef752d98dd..c6e48d108ae500 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### Enhancements
+
+- `Navigator`: Add `replace` option to `navigator.goTo()` and `navigator.goToParent()` ([#52456](https://github.com/WordPress/gutenberg/pull/52456)).
+
### Bug Fix
- `UnitControl`: Fix crash when certain units are used ([#52211](https://github.com/WordPress/gutenberg/pull/52211)).
diff --git a/packages/components/src/navigator/navigator-provider/component.tsx b/packages/components/src/navigator/navigator-provider/component.tsx
index 4c98db5b1a4d18..9bcffad6bc54bc 100644
--- a/packages/components/src/navigator/navigator-provider/component.tsx
+++ b/packages/components/src/navigator/navigator-provider/component.tsx
@@ -148,6 +148,7 @@ function UnconnectedNavigatorProvider(
focusTargetSelector,
isBack = false,
skipFocus = false,
+ replace = false,
...restOptions
} = options;
@@ -172,34 +173,38 @@ function UnconnectedNavigatorProvider(
skipFocus,
};
- if ( prevLocationHistory.length < 1 ) {
- return [ newLocation ];
+ if ( prevLocationHistory.length === 0 ) {
+ return replace ? [] : [ newLocation ];
}
- return [
- ...prevLocationHistory.slice(
- prevLocationHistory.length > MAX_HISTORY_LENGTH - 1
- ? 1
- : 0,
- -1
- ),
- // Assign `focusTargetSelector` to the previous location in history
- // (the one we just navigated from).
- {
- ...prevLocationHistory[
- prevLocationHistory.length - 1
- ],
- focusTargetSelector,
- },
- newLocation,
- ];
+ const newLocationHistory = prevLocationHistory.slice(
+ prevLocationHistory.length > MAX_HISTORY_LENGTH - 1 ? 1 : 0,
+ -1
+ );
+
+ if ( ! replace ) {
+ newLocationHistory.push(
+ // Assign `focusTargetSelector` to the previous location in history
+ // (the one we just navigated from).
+ {
+ ...prevLocationHistory[
+ prevLocationHistory.length - 1
+ ],
+ focusTargetSelector,
+ }
+ );
+ }
+
+ newLocationHistory.push( newLocation );
+
+ return newLocationHistory;
} );
},
[ goBack ]
);
- const goToParent: NavigatorContextType[ 'goToParent' ] =
- useCallback( () => {
+ const goToParent: NavigatorContextType[ 'goToParent' ] = useCallback(
+ ( options = {} ) => {
const currentPath =
currentLocationHistory.current[
currentLocationHistory.current.length - 1
@@ -214,8 +219,10 @@ function UnconnectedNavigatorProvider(
if ( parentPath === undefined ) {
return;
}
- goTo( parentPath, { isBack: true } );
- }, [ goTo ] );
+ goTo( parentPath, { ...options, isBack: true } );
+ },
+ [ goTo ]
+ );
const navigatorContextValue: NavigatorContextType = useMemo(
() => ( {
diff --git a/packages/components/src/navigator/types.ts b/packages/components/src/navigator/types.ts
index e638084e8376d5..557f8074fd42e2 100644
--- a/packages/components/src/navigator/types.ts
+++ b/packages/components/src/navigator/types.ts
@@ -14,8 +14,11 @@ export type NavigateOptions = {
focusTargetSelector?: string;
isBack?: boolean;
skipFocus?: boolean;
+ replace?: boolean;
};
+export type NavigateToParentOptions = Omit< NavigateOptions, 'isBack' >;
+
export type NavigatorLocation = NavigateOptions & {
isInitial?: boolean;
path?: string;
@@ -28,7 +31,7 @@ export type Navigator = {
params: MatchParams;
goTo: ( path: string, options?: NavigateOptions ) => void;
goBack: () => void;
- goToParent: () => void;
+ goToParent: ( options?: NavigateToParentOptions ) => void;
};
export type NavigatorContext = Navigator & {
diff --git a/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js b/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js
index 0a18c75528930c..2f237822b1ccc8 100644
--- a/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js
+++ b/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js
@@ -352,7 +352,7 @@ describe( 'Reusable blocks', () => {
expect( reusableBlockWithParagraph ).toBeTruthy();
// Convert back to regular blocks.
- await clickBlockToolbarButton( 'Select Pattern' );
+ await clickBlockToolbarButton( 'Select Edited block' );
await clickBlockToolbarButton( 'Detach pattern' );
await page.waitForXPath( selector, {
hidden: true,
diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js
index 15bc017900daa2..b7aa8bcd025af7 100644
--- a/packages/edit-post/src/components/layout/index.js
+++ b/packages/edit-post/src/components/layout/index.js
@@ -13,6 +13,7 @@ import {
EditorNotices,
EditorKeyboardShortcutsRegister,
EditorSnackbars,
+ PostSyncStatusModal,
store as editorStore,
} from '@wordpress/editor';
import { useSelect, useDispatch } from '@wordpress/data';
@@ -291,6 +292,7 @@ function Layout( { styles } ) {
+
diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js
index fcd28948ccbb3f..e2d9e2680b263d 100644
--- a/packages/edit-site/src/components/block-editor/index.js
+++ b/packages/edit-site/src/components/block-editor/index.js
@@ -1,7 +1,3 @@
-/**
- * External dependencies
- */
-
/**
* WordPress dependencies
*/
diff --git a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js
index 4241c7f55cb677..ab94f8f86000b9 100644
--- a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js
+++ b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { useSelect } from '@wordpress/data';
+import { useDispatch, useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';
import { store as coreStore } from '@wordpress/core-data';
/**
@@ -11,16 +11,21 @@ import { store as editSiteStore } from '../../store';
import { unlock } from '../../lock-unlock';
import inserterMediaCategories from './inserter-media-categories';
-export default function useSiteEditorSettings( templateType ) {
- const { storedSettings, canvasMode } = useSelect( ( select ) => {
- const { getSettings, getCanvasMode } = unlock(
- select( editSiteStore )
- );
- return {
- storedSettings: getSettings(),
- canvasMode: getCanvasMode(),
- };
- }, [] );
+export default function useSiteEditorSettings() {
+ const { setIsInserterOpened } = useDispatch( editSiteStore );
+ const { storedSettings, canvasMode, templateType } = useSelect(
+ ( select ) => {
+ const { getSettings, getCanvasMode, getEditedPostType } = unlock(
+ select( editSiteStore )
+ );
+ return {
+ storedSettings: getSettings( setIsInserterOpened ),
+ canvasMode: getCanvasMode(),
+ templateType: getEditedPostType(),
+ };
+ },
+ [ setIsInserterOpened ]
+ );
const settingsBlockPatterns =
storedSettings.__experimentalAdditionalBlockPatterns ?? // WP 6.0
diff --git a/packages/edit-site/src/components/layout/style.scss b/packages/edit-site/src/components/layout/style.scss
index 2fe210327661d4..e5661c61eb7897 100644
--- a/packages/edit-site/src/components/layout/style.scss
+++ b/packages/edit-site/src/components/layout/style.scss
@@ -61,6 +61,7 @@
.edit-site-layout__sidebar {
z-index: z-index(".edit-site-layout__sidebar");
width: 100vw;
+ flex-shrink: 0;
@include break-medium {
width: $nav-sidebar-width;
diff --git a/packages/edit-site/src/components/page-patterns/grid.js b/packages/edit-site/src/components/page-patterns/grid.js
index 1902b36982c144..47bcdc8a1f768e 100644
--- a/packages/edit-site/src/components/page-patterns/grid.js
+++ b/packages/edit-site/src/components/page-patterns/grid.js
@@ -1,26 +1,115 @@
/**
* WordPress dependencies
*/
-import { __experimentalText as Text } from '@wordpress/components';
-import { useRef } from '@wordpress/element';
-import { __, sprintf } from '@wordpress/i18n';
+import {
+ __experimentalHStack as HStack,
+ __experimentalText as Text,
+ Button,
+} from '@wordpress/components';
+import { useRef, useState, useMemo } from '@wordpress/element';
+import { __, _x, _n, sprintf } from '@wordpress/i18n';
+import { useAsyncList } from '@wordpress/compose';
/**
* Internal dependencies
*/
import GridItem from './grid-item';
-const PAGE_SIZE = 100;
+const PAGE_SIZE = 20;
+
+function Pagination( { currentPage, numPages, changePage, totalItems } ) {
+ return (
+
+
+ {
+ // translators: %s: Total number of patterns.
+ sprintf(
+ // translators: %s: Total number of patterns.
+ _n( '%s item', '%s items', totalItems ),
+ totalItems
+ )
+ }
+
+
+ changePage( 1 ) }
+ disabled={ currentPage === 1 }
+ aria-label={ __( 'First page' ) }
+ >
+ «
+
+ changePage( currentPage - 1 ) }
+ disabled={ currentPage === 1 }
+ aria-label={ __( 'Previous page' ) }
+ >
+ ‹
+
+
+
+ { sprintf(
+ // translators: %1$s: Current page number, %2$s: Total number of pages.
+ _x( '%1$s of %2$s', 'paging' ),
+ currentPage,
+ numPages
+ ) }
+
+
+ changePage( currentPage + 1 ) }
+ disabled={ currentPage === numPages }
+ aria-label={ __( 'Next page' ) }
+ >
+ ›
+
+ changePage( numPages ) }
+ disabled={ currentPage === numPages }
+ aria-label={ __( 'Last page' ) }
+ >
+ »
+
+
+
+ );
+}
export default function Grid( { categoryId, items, ...props } ) {
+ const [ currentPage, setCurrentPage ] = useState( 1 );
const gridRef = useRef();
+ const totalItems = items.length;
+ const pageIndex = currentPage - 1;
- if ( ! items?.length ) {
+ const list = useMemo(
+ () =>
+ items.slice(
+ pageIndex * PAGE_SIZE,
+ pageIndex * PAGE_SIZE + PAGE_SIZE
+ ),
+ [ pageIndex, items ]
+ );
+
+ const asyncList = useAsyncList( list, { step: 10 } );
+
+ if ( ! list?.length ) {
return null;
}
- const list = items.slice( 0, PAGE_SIZE );
- const restLength = items.length - PAGE_SIZE;
+ const numPages = Math.ceil( items.length / PAGE_SIZE );
+ const changePage = ( page ) => {
+ const scrollContainer = document.querySelector( '.edit-site-patterns' );
+ scrollContainer?.scrollTo( 0, 0 );
+
+ setCurrentPage( page );
+ };
return (
<>
@@ -30,7 +119,7 @@ export default function Grid( { categoryId, items, ...props } ) {
{ ...props }
ref={ gridRef }
>
- { list.map( ( item ) => (
+ { asyncList.map( ( item ) => (
) ) }
- { restLength > 0 && (
-
- { sprintf(
- /* translators: %d: number of patterns */
- __( '+ %d more patterns discoverable by searching' ),
- restLength
- ) }
-
+ { numPages > 1 && (
+
) }
>
);
diff --git a/packages/edit-site/src/components/page-patterns/patterns-list.js b/packages/edit-site/src/components/page-patterns/patterns-list.js
index 7bf2a9d5065841..f4fefed4a2d3e0 100644
--- a/packages/edit-site/src/components/page-patterns/patterns-list.js
+++ b/packages/edit-site/src/components/page-patterns/patterns-list.js
@@ -15,7 +15,7 @@ import {
import { __, isRTL } from '@wordpress/i18n';
import { chevronLeft, chevronRight } from '@wordpress/icons';
import { privateApis as routerPrivateApis } from '@wordpress/router';
-import { useViewportMatch, useAsyncList } from '@wordpress/compose';
+import { useViewportMatch } from '@wordpress/compose';
/**
* Internal dependencies
@@ -70,7 +70,6 @@ export default function PatternsList( { categoryId, type } ) {
const hasPatterns = patterns.length;
const title = SYNC_FILTERS[ syncFilter ];
const description = SYNC_DESCRIPTIONS[ syncFilter ];
- const shownPatterns = useAsyncList( patterns );
return (
@@ -132,7 +131,7 @@ export default function PatternsList( { categoryId, type } ) {
{ syncFilter !== 'all' && (
-
+
{ title }
{ description ? (
@@ -145,7 +144,7 @@ export default function PatternsList( { categoryId, type } ) {
{ hasPatterns && (
diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss
index 79731999f46efa..d1fbedb141f701 100644
--- a/packages/edit-site/src/components/page-patterns/style.scss
+++ b/packages/edit-site/src/components/page-patterns/style.scss
@@ -1,6 +1,8 @@
.edit-site-patterns {
- background: rgba(0, 0, 0, 0.15);
+ border: 1px solid $gray-800;
+ background: none;
margin: $header-height 0 0;
+ border-radius: 0;
.components-base-control {
width: 100%;
@include break-medium {
@@ -59,6 +61,23 @@
background: $gray-700;
color: $gray-100;
}
+
+ .edit-site-patterns__grid-pagination {
+ width: fit-content;
+ .components-button.is-tertiary {
+ width: $button-size-compact;
+ height: $button-size-compact;
+ color: $gray-100;
+ background-color: $gray-800;
+ &:disabled {
+ color: $gray-600;
+ background: none;
+ }
+ &:hover:not(:disabled) {
+ background-color: $gray-700;
+ }
+ }
+ }
}
.edit-site-patterns__section-header {
@@ -74,6 +93,7 @@
// Small top padding required to avoid cutting off the visible outline
// when hovering items.
padding-top: $border-width-focus-fallback;
+ margin-top: 0;
margin-bottom: $grid-unit-40;
@include break-large {
grid-template-columns: 1fr 1fr;
diff --git a/packages/edit-site/src/components/page-template-parts/add-new-template-part.js b/packages/edit-site/src/components/page-template-parts/add-new-template-part.js
new file mode 100644
index 00000000000000..d2b52a88701fda
--- /dev/null
+++ b/packages/edit-site/src/components/page-template-parts/add-new-template-part.js
@@ -0,0 +1,57 @@
+/**
+ * WordPress dependencies
+ */
+import { privateApis as routerPrivateApis } from '@wordpress/router';
+import { useSelect } from '@wordpress/data';
+import { store as coreStore } from '@wordpress/core-data';
+import { useState } from '@wordpress/element';
+import { Button } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../lock-unlock';
+import { store as editSiteStore } from '../../store';
+import CreateTemplatePartModal from '../create-template-part-modal';
+
+const { useHistory } = unlock( routerPrivateApis );
+
+export default function AddNewTemplatePart() {
+ const { canCreate, postType } = useSelect( ( select ) => {
+ const { supportsTemplatePartsMode } =
+ select( editSiteStore ).getSettings();
+ return {
+ canCreate: ! supportsTemplatePartsMode,
+ postType: select( coreStore ).getPostType( 'wp_template_part' ),
+ };
+ }, [] );
+ const [ isModalOpen, setIsModalOpen ] = useState( false );
+ const history = useHistory();
+
+ if ( ! canCreate || ! postType ) {
+ return null;
+ }
+
+ return (
+ <>
+ setIsModalOpen( true ) }>
+ { postType.labels.add_new_item }
+
+ { isModalOpen && (
+ setIsModalOpen( false ) }
+ blocks={ [] }
+ onCreate={ ( templatePart ) => {
+ setIsModalOpen( false );
+ history.push( {
+ postId: templatePart.id,
+ postType: 'wp_template_part',
+ canvas: 'edit',
+ } );
+ } }
+ onError={ () => setIsModalOpen( false ) }
+ />
+ ) }
+ >
+ );
+}
diff --git a/packages/edit-site/src/components/page-template-parts/index.js b/packages/edit-site/src/components/page-template-parts/index.js
index 7e9c8cb6dd6e16..24b042ed92001e 100644
--- a/packages/edit-site/src/components/page-template-parts/index.js
+++ b/packages/edit-site/src/components/page-template-parts/index.js
@@ -7,8 +7,7 @@ import {
__experimentalVStack as VStack,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
-import { useSelect } from '@wordpress/data';
-import { store as coreStore, useEntityRecords } from '@wordpress/core-data';
+import { useEntityRecords } from '@wordpress/core-data';
import { decodeEntities } from '@wordpress/html-entities';
/**
@@ -19,8 +18,7 @@ import Table from '../table';
import Link from '../routes/link';
import AddedBy from '../list/added-by';
import TemplateActions from '../template-actions';
-import AddNewTemplate from '../add-new-template';
-import { store as editSiteStore } from '../../store';
+import AddNewTemplatePart from './add-new-template-part';
export default function PageTemplateParts() {
const { records: templateParts } = useEntityRecords(
@@ -31,15 +29,6 @@ export default function PageTemplateParts() {
}
);
- const { canCreate } = useSelect( ( select ) => {
- const { supportsTemplatePartsMode } =
- select( editSiteStore ).getSettings();
- return {
- postType: select( coreStore ).getPostType( 'wp_template_part' ),
- canCreate: ! supportsTemplatePartsMode,
- };
- } );
-
const columns = [
{
header: __( 'Template Part' ),
@@ -87,15 +76,7 @@ export default function PageTemplateParts() {
return (
- )
- }
+ actions={ }
>
{ templateParts && (
diff --git a/packages/edit-site/src/components/resizable-frame/index.js b/packages/edit-site/src/components/resizable-frame/index.js
index a13881e7905ffa..1bb9315a07e38a 100644
--- a/packages/edit-site/src/components/resizable-frame/index.js
+++ b/packages/edit-site/src/components/resizable-frame/index.js
@@ -9,9 +9,12 @@ import classnames from 'classnames';
import { useState, useRef, useEffect } from '@wordpress/element';
import {
ResizableBox,
+ Tooltip,
__unstableMotion as motion,
} from '@wordpress/components';
-import { useDispatch } from '@wordpress/data';
+import { useInstanceId } from '@wordpress/compose';
+import { useDispatch, useSelect } from '@wordpress/data';
+import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
@@ -33,7 +36,7 @@ const HANDLE_STYLES_OVERRIDE = {
};
// The minimum width of the frame (in px) while resizing.
-const FRAME_MIN_WIDTH = 340;
+const FRAME_MIN_WIDTH = 320;
// The reference width of the frame (in px) used to calculate the aspect ratio.
const FRAME_REFERENCE_WIDTH = 1300;
// 9 : 19.5 is the target aspect ratio enforced (when possible) while resizing.
@@ -42,6 +45,8 @@ const FRAME_TARGET_ASPECT_RATIO = 9 / 19.5;
// viewport's edge. If the frame is resized to be closer to the viewport's edge
// than this distance, then "canvas mode" will be enabled.
const SNAP_TO_EDIT_CANVAS_MODE_THRESHOLD = 200;
+// Default size for the `frameSize` state.
+const INITIAL_FRAME_SIZE = { width: '100%', height: '100%' };
function calculateNewHeight( width, initialAspectRatio ) {
const lerp = ( a, b, amount ) => {
@@ -78,22 +83,27 @@ function ResizableFrame( {
oversizedClassName,
innerContentStyle,
} ) {
- const [ frameSize, setFrameSize ] = useState( {
- width: '100%',
- height: '100%',
- } );
+ const [ frameSize, setFrameSize ] = useState( INITIAL_FRAME_SIZE );
// The width of the resizable frame when a new resize gesture starts.
const [ startingWidth, setStartingWidth ] = useState();
const [ isResizing, setIsResizing ] = useState( false );
- const [ isHovering, setIsHovering ] = useState( false );
+ const [ shouldShowHandle, setShouldShowHandle ] = useState( false );
const [ isOversized, setIsOversized ] = useState( false );
const [ resizeRatio, setResizeRatio ] = useState( 1 );
+ const canvasMode = useSelect(
+ ( select ) => unlock( select( editSiteStore ) ).getCanvasMode(),
+ []
+ );
const { setCanvasMode } = unlock( useDispatch( editSiteStore ) );
const initialAspectRatioRef = useRef( null );
// The width of the resizable frame on initial render.
const initialComputedWidthRef = useRef( null );
const FRAME_TRANSITION = { type: 'tween', duration: isResizing ? 0 : 0.5 };
const frameRef = useRef( null );
+ const resizableHandleHelpId = useInstanceId(
+ ResizableFrame,
+ 'edit-site-resizable-frame-handle-help'
+ );
// Remember frame dimensions on initial render.
useEffect( () => {
@@ -154,13 +164,40 @@ function ResizableFrame( {
if ( remainingWidth > SNAP_TO_EDIT_CANVAS_MODE_THRESHOLD ) {
// Reset the initial aspect ratio if the frame is resized slightly
// above the sidebar but not far enough to trigger full screen.
- setFrameSize( { width: '100%', height: '100%' } );
+ setFrameSize( INITIAL_FRAME_SIZE );
} else {
// Trigger full screen if the frame is resized far enough to the left.
setCanvasMode( 'edit' );
}
};
+ // Handle resize by arrow keys
+ const handleResizableHandleKeyDown = ( event ) => {
+ if ( ! [ 'ArrowLeft', 'ArrowRight' ].includes( event.key ) ) {
+ return;
+ }
+
+ event.preventDefault();
+
+ const step = 20 * ( event.shiftKey ? 5 : 1 );
+ const delta = step * ( event.key === 'ArrowLeft' ? 1 : -1 );
+ const newWidth = Math.min(
+ Math.max(
+ FRAME_MIN_WIDTH,
+ frameRef.current.resizable.offsetWidth + delta
+ ),
+ initialComputedWidthRef.current
+ );
+
+ setFrameSize( {
+ width: newWidth,
+ height: calculateNewHeight(
+ newWidth,
+ initialAspectRatioRef.current
+ ),
+ } );
+ };
+
const frameAnimationVariants = {
default: {
flexGrow: 0,
@@ -173,16 +210,26 @@ function ResizableFrame( {
};
const resizeHandleVariants = {
- default: {
+ hidden: {
+ opacity: 0,
+ left: 0,
+ },
+ visible: {
opacity: 1,
left: -16,
},
- resizing: {
+ active: {
opacity: 1,
left: -16,
scaleY: 1.3,
},
};
+ const currentResizeHandleVariant = ( () => {
+ if ( isResizing ) {
+ return 'active';
+ }
+ return shouldShowHandle ? 'visible' : 'hidden';
+ } )();
return (
setIsHovering( true ) }
- onMouseOut={ () => setIsHovering( false ) }
+ onFocus={ () => setShouldShowHandle( true ) }
+ onBlur={ () => setShouldShowHandle( false ) }
+ onMouseOver={ () => setShouldShowHandle( true ) }
+ onMouseOut={ () => setShouldShowHandle( false ) }
handleComponent={ {
- left:
- isHovering || isResizing ? (
-
- ) : null,
+ left: canvasMode === 'view' && (
+ <>
+
+ { /* Disable reason: role="separator" does in fact support aria-valuenow */ }
+ { /* eslint-disable-next-line jsx-a11y/role-supports-aria-props */ }
+
+
+
+ { __(
+ 'Use left and right arrow keys to resize the canvas. Hold shift to resize in larger increments.'
+ ) }
+
+ >
+ ),
} }
onResizeStart={ handleResizeStart }
onResize={ handleResize }
diff --git a/packages/edit-site/src/components/resizable-frame/style.scss b/packages/edit-site/src/components/resizable-frame/style.scss
index 9a959afe845e47..596304be8d6b9e 100644
--- a/packages/edit-site/src/components/resizable-frame/style.scss
+++ b/packages/edit-site/src/components/resizable-frame/style.scss
@@ -1,6 +1,23 @@
.edit-site-resizable-frame__inner {
position: relative;
+ &.edit-site-layout__resizable-frame-oversized {
+ @at-root {
+ body:has(&) {
+ .edit-site-site-hub {
+ .edit-site-site-hub__site-title,
+ .edit-site-site-hub_toggle-command-center {
+ opacity: 0 !important;
+ }
+
+ .edit-site-site-hub__view-mode-toggle-container {
+ background-color: transparent;
+ }
+ }
+ }
+ }
+ }
+
&.is-resizing {
@at-root {
body:has(&) {
@@ -30,11 +47,13 @@
.edit-site-resizable-frame__handle {
align-items: center;
background-color: rgba($gray-700, 0.4);
+ border: 0;
border-radius: $grid-unit-05;
cursor: col-resize;
display: flex;
height: $grid-unit-80;
justify-content: flex-end;
+ padding: 0;
position: absolute;
top: calc(50% - #{$grid-unit-40});
width: $grid-unit-05;
@@ -56,16 +75,14 @@
width: $grid-unit-40;
}
- &:hover,
- .is-resizing & {
- background-color: var(--wp-admin-theme-color);
+ &:focus-visible {
+ // Works with Windows high contrast mode while also hiding weird outline in Safari.
+ outline: 2px solid transparent;
}
- .edit-site-resizable-frame__handle-label {
- background: var(--wp-admin-theme-color);
- border-radius: 2px;
- color: #fff;
- margin-right: $grid-unit-10;
- padding: 4px 8px;
+ &:hover,
+ &:focus,
+ &.is-resizing {
+ background-color: var(--wp-admin-theme-color);
}
}
diff --git a/packages/edit-site/src/components/sidebar-navigation-item/style.scss b/packages/edit-site/src/components/sidebar-navigation-item/style.scss
index d81b6764f7b721..14dbc54efe5238 100644
--- a/packages/edit-site/src/components/sidebar-navigation-item/style.scss
+++ b/packages/edit-site/src/components/sidebar-navigation-item/style.scss
@@ -11,6 +11,10 @@
&[aria-current] {
color: $gray-200;
background: $gray-800;
+
+ .edit-site-sidebar-navigation-item__drilldown-indicator {
+ fill: $gray-200;
+ }
}
&[aria-current] {
@@ -19,7 +23,7 @@
}
.edit-site-sidebar-navigation-item__drilldown-indicator {
- fill: $gray-700;
+ fill: $gray-600;
}
&:is(a) {
@@ -31,6 +35,11 @@
box-shadow: none;
outline: none;
}
+
+ &:focus-visible {
+ box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
+ outline: 2px solid transparent;
+ }
}
&.with-suffix {
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-main/template-part-hint.js b/packages/edit-site/src/components/sidebar-navigation-screen-main/template-part-hint.js
index 8fbe74f81bb4d9..c6f270465b86f3 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-main/template-part-hint.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-main/template-part-hint.js
@@ -28,9 +28,7 @@ export default function TemplatePartHint() {
setPreference( 'core', PREFERENCE_NAME, false );
} }
>
- { __(
- 'Looking for template parts? You can now find them in the new "Patterns" page.'
- ) }
+ { __( 'Looking for template parts? Find them in "Patterns".' ) }
);
}
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/more-menu.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/more-menu.js
index 08c17304ca38b5..4de10b8377da94 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/more-menu.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/more-menu.js
@@ -33,6 +33,7 @@ export default function ScreenNavigationMoreMenu( props ) {
<>
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js
index 6b6fc8587228f6..f52204f3a18430 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js
@@ -23,13 +23,13 @@ export default function SingleNavigationMenu( {
-
+
>
}
title={ decodeEntities( menuTitle ) }
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js
index 567bf91aca69fd..331221dde79859 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js
@@ -58,13 +58,16 @@ export default function SidebarNavigationScreenPages() {
templates?.find( ( template ) => template.slug === 'home' ) ||
templates?.find( ( template ) => template.slug === 'index' );
+ const getPostsPageTemplate = () =>
+ templates?.find( ( template ) => template.slug === 'home' ) ||
+ templates?.find( ( template ) => template.slug === 'index' );
+
const pagesAndTemplates = pages?.concat( dynamicPageTemplates, [
homeTemplate,
] );
const { frontPage, postsPage } = useSelect( ( select ) => {
const { getEntityRecord } = select( coreStore );
-
const siteSettings = getEntityRecord( 'root', 'site' );
return {
frontPage: siteSettings?.page_on_front,
@@ -106,6 +109,27 @@ export default function SidebarNavigationScreenPages() {
setShowAddPage( false );
};
+ const getPageProps = ( id ) => {
+ let itemIcon = page;
+ const postsPageTemplateId =
+ postsPage && postsPage === id ? getPostsPageTemplate()?.id : null;
+
+ switch ( id ) {
+ case frontPage:
+ itemIcon = home;
+ break;
+ case postsPage:
+ itemIcon = verse;
+ break;
+ }
+
+ return {
+ icon: itemIcon,
+ postType: postsPageTemplateId ? 'wp_template' : 'page',
+ postId: postsPageTemplateId || id,
+ };
+ };
+
return (
<>
{ showAddPage && (
@@ -152,34 +176,20 @@ export default function SidebarNavigationScreenPages() {
) }
- { reorderedPages?.map( ( item ) => {
- let itemIcon;
- switch ( item.id ) {
- case frontPage:
- itemIcon = home;
- break;
- case postsPage:
- itemIcon = verse;
- break;
- default:
- itemIcon = page;
- }
- return (
-
-
- { decodeEntities(
- item?.title?.rendered ||
- __( '(no title)' )
- ) }
-
-
- );
- } ) }
+ { reorderedPages?.map( ( { id, title } ) => (
+
+
+ { decodeEntities(
+ title?.rendered ||
+ __( '(no title)' )
+ ) }
+
+
+ ) ) }
) }
>
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menu.js b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menu.js
index f451c17e00adb7..a57f5e025907c4 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menu.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menu.js
@@ -19,8 +19,9 @@ export default function TemplatePartNavigationMenu( { id } ) {
<>
{ title?.rendered || __( 'Navigation' ) }
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menus.js b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menus.js
index 418b1d4423b202..d04eba2e435b45 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menus.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menus.js
@@ -22,8 +22,9 @@ export default function TemplatePartNavigationMenus( { menus } ) {
<>
{ __( 'Navigation' ) }
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js
index d3fc15358027b3..c7d3a3b77d1514 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js
@@ -107,7 +107,7 @@ export default function SidebarNavigationScreenPatterns() {
const { templatePartAreas, hasTemplateParts, isLoading } =
useTemplatePartAreas();
const { patternCategories, hasPatterns } = usePatternCategories();
- const { myPatterns, hasPatterns: hasMyPatterns } = useMyPatterns();
+ const { myPatterns } = useMyPatterns();
const isTemplatePartsMode = useSelect( ( select ) => {
const settings = select( editSiteStore ).getSettings();
@@ -153,23 +153,25 @@ export default function SidebarNavigationScreenPatterns() {
) }
- { hasMyPatterns && (
-
-
-
- ) }
+
+
+
{ hasTemplateParts && (
{ ! isRoot && ! backPath && (
- {
+ if ( navigator.location.isInitial ) {
+ navigator.goToParent( { replace: true } );
+ } else {
+ navigator.goBack();
+ }
+ } }
+ icon={ icon }
label={ __( 'Back' ) }
showTooltip={ false }
/>
) }
{ ! isRoot && backPath && (
goTo( backPath, { isBack: true } ) }
+ onClick={ () =>
+ navigator.goTo( backPath, { isBack: true } )
+ }
icon={ icon }
label={ __( 'Back' ) }
showTooltip={ false }
diff --git a/packages/edit-site/src/components/site-hub/index.js b/packages/edit-site/src/components/site-hub/index.js
index 897924a92a12bd..1e6eddabe90a28 100644
--- a/packages/edit-site/src/components/site-hub/index.js
+++ b/packages/edit-site/src/components/site-hub/index.js
@@ -51,7 +51,10 @@ const SiteHub = forwardRef( ( props, ref ) => {
const { open: openCommandCenter } = useDispatch( commandsStore );
const disableMotion = useReducedMotion();
- const { setCanvasMode } = unlock( useDispatch( editSiteStore ) );
+ const {
+ setCanvasMode,
+ __experimentalSetPreviewDeviceType: setPreviewDeviceType,
+ } = unlock( useDispatch( editSiteStore ) );
const { clearSelectedBlock } = useDispatch( blockEditorStore );
const isBackToDashboardButton = canvasMode === 'view';
const siteIconButtonProps = isBackToDashboardButton
@@ -67,6 +70,7 @@ const SiteHub = forwardRef( ( props, ref ) => {
event.preventDefault();
if ( canvasMode === 'edit' ) {
clearSelectedBlock();
+ setPreviewDeviceType( 'desktop' );
setCanvasMode( 'view' );
}
},
diff --git a/packages/edit-site/src/components/site-hub/style.scss b/packages/edit-site/src/components/site-hub/style.scss
index d0689fec4efa9e..70d256e2c4a560 100644
--- a/packages/edit-site/src/components/site-hub/style.scss
+++ b/packages/edit-site/src/components/site-hub/style.scss
@@ -8,6 +8,11 @@
gap: 0;
}
+ .edit-site-site-hub__site-title,
+ .edit-site-site-hub_toggle-command-center {
+ transition: opacity ease 0.1s;
+ }
+
.edit-site-site-hub__site-view-link {
flex-grow: 0;
@include break-mobile() {
diff --git a/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js b/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js
index 45f7eb1b044c1f..86928c1920a948 100644
--- a/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js
+++ b/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js
@@ -36,6 +36,12 @@ export function getPathFromURL( urlParams ) {
return path;
}
+function isSubset( subset, superset ) {
+ return Object.entries( subset ).every( ( [ key, value ] ) => {
+ return superset[ key ] === value;
+ } );
+}
+
export default function useSyncPathWithURL() {
const history = useHistory();
const { params: urlParams } = useLocation();
@@ -44,76 +50,77 @@ export default function useSyncPathWithURL() {
params: navigatorParams,
goTo,
} = useNavigator();
- const currentUrlParams = useRef( urlParams );
- const currentPath = useRef( navigatorLocation.path );
const isMounting = useRef( true );
- useEffect( () => {
- // The navigatorParams are only initially filled properly when the
- // navigator screens mount. so we ignore the first synchronisation.
- if ( isMounting.current ) {
- isMounting.current = false;
- return;
- }
-
- function updateUrlParams( newUrlParams ) {
- if (
- Object.entries( newUrlParams ).every( ( [ key, value ] ) => {
- return currentUrlParams.current[ key ] === value;
- } )
- ) {
+ useEffect(
+ () => {
+ // The navigatorParams are only initially filled properly when the
+ // navigator screens mount. so we ignore the first synchronisation.
+ if ( isMounting.current ) {
+ isMounting.current = false;
return;
}
- const updatedParams = {
- ...currentUrlParams.current,
- ...newUrlParams,
- };
- currentUrlParams.current = updatedParams;
- history.push( updatedParams );
- }
- if ( navigatorParams?.postType && navigatorParams?.postId ) {
- updateUrlParams( {
- postType: navigatorParams?.postType,
- postId: navigatorParams?.postId,
- path: undefined,
- } );
- } else if (
- navigatorLocation.path.startsWith( '/page/' ) &&
- navigatorParams?.postId
- ) {
- updateUrlParams( {
- postType: 'page',
- postId: navigatorParams?.postId,
- path: undefined,
- } );
- } else if ( navigatorLocation.path === '/patterns' ) {
- updateUrlParams( {
- postType: undefined,
- postId: undefined,
- canvas: undefined,
- path: navigatorLocation.path,
- } );
- } else {
- updateUrlParams( {
- postType: undefined,
- postId: undefined,
- categoryType: undefined,
- categoryId: undefined,
- path:
- navigatorLocation.path === '/'
- ? undefined
- : navigatorLocation.path,
- } );
- }
- }, [ navigatorLocation?.path, navigatorParams, history ] );
+ function updateUrlParams( newUrlParams ) {
+ if ( isSubset( newUrlParams, urlParams ) ) {
+ return;
+ }
+ const updatedParams = {
+ ...urlParams,
+ ...newUrlParams,
+ };
+ history.push( updatedParams );
+ }
- useEffect( () => {
- currentUrlParams.current = urlParams;
- const path = getPathFromURL( urlParams );
- if ( currentPath.current !== path ) {
- currentPath.current = path;
- goTo( path );
- }
- }, [ urlParams, goTo ] );
+ if ( navigatorParams?.postType && navigatorParams?.postId ) {
+ updateUrlParams( {
+ postType: navigatorParams?.postType,
+ postId: navigatorParams?.postId,
+ path: undefined,
+ } );
+ } else if (
+ navigatorLocation.path.startsWith( '/page/' ) &&
+ navigatorParams?.postId
+ ) {
+ updateUrlParams( {
+ postType: 'page',
+ postId: navigatorParams?.postId,
+ path: undefined,
+ } );
+ } else if ( navigatorLocation.path === '/patterns' ) {
+ updateUrlParams( {
+ postType: undefined,
+ postId: undefined,
+ canvas: undefined,
+ path: navigatorLocation.path,
+ } );
+ } else {
+ updateUrlParams( {
+ postType: undefined,
+ postId: undefined,
+ categoryType: undefined,
+ categoryId: undefined,
+ path:
+ navigatorLocation.path === '/'
+ ? undefined
+ : navigatorLocation.path,
+ } );
+ }
+ },
+ // Trigger only when navigator changes to prevent infinite loops.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [ navigatorLocation?.path, navigatorParams ]
+ );
+
+ useEffect(
+ () => {
+ const path = getPathFromURL( urlParams );
+ if ( navigatorLocation.path !== path ) {
+ goTo( path );
+ }
+ },
+ // Trigger only when URL changes to prevent infinite loops.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [ urlParams ]
+ );
}
diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js
index b037397cfdd4cf..aa20f9d6ae9fae 100644
--- a/packages/editor/src/components/index.js
+++ b/packages/editor/src/components/index.js
@@ -51,7 +51,10 @@ export { default as PostSlugCheck } from './post-slug/check';
export { default as PostSticky } from './post-sticky';
export { default as PostStickyCheck } from './post-sticky/check';
export { default as PostSwitchToDraftButton } from './post-switch-to-draft-button';
-export { default as PostSyncStatus } from './post-sync-status';
+export {
+ default as PostSyncStatus,
+ PostSyncStatusModal,
+} from './post-sync-status';
export { default as PostTaxonomies } from './post-taxonomies';
export { FlatTermSelector as PostTaxonomiesFlatTermSelector } from './post-taxonomies/flat-term-selector';
export { HierarchicalTermSelector as PostTaxonomiesHierarchicalTermSelector } from './post-taxonomies/hierarchical-term-selector';
diff --git a/packages/editor/src/components/post-sync-status/index.js b/packages/editor/src/components/post-sync-status/index.js
index 0600ece9531731..8219ef7c0e4f54 100644
--- a/packages/editor/src/components/post-sync-status/index.js
+++ b/packages/editor/src/components/post-sync-status/index.js
@@ -1,9 +1,18 @@
/**
* WordPress dependencies
*/
-import { useSelect } from '@wordpress/data';
+import { useSelect, useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
-import { PanelRow } from '@wordpress/components';
+import {
+ PanelRow,
+ Modal,
+ Button,
+ __experimentalHStack as HStack,
+ __experimentalVStack as VStack,
+ ToggleControl,
+} from '@wordpress/components';
+import { useEffect, useState } from '@wordpress/element';
+import { ReusableBlocksRenameHint } from '@wordpress/block-editor';
/**
* Internal dependencies
@@ -11,24 +20,108 @@ import { PanelRow } from '@wordpress/components';
import { store as editorStore } from '../../store';
export default function PostSyncStatus() {
- const { syncStatus, postType } = useSelect( ( select ) => {
+ const { syncStatus, postType, meta } = useSelect( ( select ) => {
const { getEditedPostAttribute } = select( editorStore );
return {
syncStatus: getEditedPostAttribute( 'wp_pattern_sync_status' ),
+ meta: getEditedPostAttribute( 'meta' ),
postType: getEditedPostAttribute( 'type' ),
};
- }, [] );
+ } );
+
if ( postType !== 'wp_block' ) {
return null;
}
- const isFullySynced = ! syncStatus;
+ // When the post is first created, the top level wp_pattern_sync_status is not set so get meta value instead.
+ const currentSyncStatus =
+ meta?.wp_pattern_sync_status === 'unsynced' ? 'unsynced' : syncStatus;
return (
{ __( 'Sync status' ) }
- { isFullySynced ? __( 'Fully synced' ) : __( 'Not synced' ) }
+ { currentSyncStatus === 'unsynced'
+ ? __( 'Not synced' )
+ : __( 'Fully synced' ) }
);
}
+
+export function PostSyncStatusModal() {
+ const { editPost } = useDispatch( editorStore );
+ const [ isModalOpen, setIsModalOpen ] = useState( false );
+ const [ syncType, setSyncType ] = useState( undefined );
+
+ const { postType, isNewPost } = useSelect( ( select ) => {
+ const { getEditedPostAttribute, isCleanNewPost } =
+ select( editorStore );
+ return {
+ postType: getEditedPostAttribute( 'type' ),
+ isNewPost: isCleanNewPost(),
+ };
+ }, [] );
+
+ useEffect( () => {
+ if ( isNewPost && postType === 'wp_block' ) {
+ setIsModalOpen( true );
+ }
+ // We only want the modal to open when the page is first loaded.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [] );
+
+ const setSyncStatus = () => {
+ editPost( {
+ meta: {
+ wp_pattern_sync_status: syncType,
+ },
+ } );
+ };
+
+ if ( postType !== 'wp_block' || ! isNewPost ) {
+ return null;
+ }
+
+ return (
+ <>
+ { isModalOpen && (
+ {
+ setIsModalOpen( false );
+ } }
+ overlayClassName="reusable-blocks-menu-items__convert-modal"
+ >
+
+
+ ) }
+ >
+ );
+}
diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js
index 064ed81943de1f..384fa4c653e539 100644
--- a/packages/format-library/src/link/inline.js
+++ b/packages/format-library/src/link/inline.js
@@ -243,7 +243,7 @@ function InlineLinkUI( {
return createInterpolateElement(
sprintf(
/* translators: %s: search term. */
- __( 'Create Page: %s ' ),
+ __( 'Create page: %s ' ),
searchTerm
),
{ mark: }
diff --git a/test/e2e/specs/editor/blocks/navigation.spec.js b/test/e2e/specs/editor/blocks/navigation.spec.js
index 8f45e8ddcf7e34..b7632530ab1504 100644
--- a/test/e2e/specs/editor/blocks/navigation.spec.js
+++ b/test/e2e/specs/editor/blocks/navigation.spec.js
@@ -1300,7 +1300,9 @@ class LinkControl {
await expect( result ).toBeVisible();
return result
- .locator( '.block-editor-link-control__search-item-title' ) // this is the only way to get the label text without the URL.
+ .locator(
+ '.components-menu-item__info-wrapper .components-menu-item__item'
+ ) // this is the only way to get the label text without the URL.
.innerText();
}
}