From 3c9d2cd2fe2d622c251b45ea09855f23bb86f49b Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Tue, 13 Feb 2024 08:38:09 -0500 Subject: [PATCH 01/22] Font Library: Replace infinite scroll by pagination (#58794) * Eliminate and render fonts with pagination instead of as a large list Co-authored-by: pbking Co-authored-by: carolinan Co-authored-by: matiasbenedetto Co-authored-by: t-hamano Co-authored-by: afercia Co-authored-by: colorful-tones Co-authored-by: jasmussen Co-authored-by: okmttdhr --- .../collection-font-details.js | 2 +- .../font-library-modal/font-collection.js | 131 ++++++++++++++++-- .../font-library-modal/fonts-grid.js | 59 -------- .../font-library-modal/installed-fonts.js | 48 ++++--- .../font-library-modal/style.scss | 7 +- 5 files changed, 146 insertions(+), 101 deletions(-) delete mode 100644 packages/edit-site/src/components/global-styles/font-library-modal/fonts-grid.js diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/collection-font-details.js b/packages/edit-site/src/components/global-styles/font-library-modal/collection-font-details.js index de0c1cfa16ec6e..a6962952661939 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/collection-font-details.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/collection-font-details.js @@ -48,7 +48,7 @@ function CollectionFontDetails( { /> ) ) } - + ); } diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js index 4a14ee245694b8..6236ea8fe3f246 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js @@ -1,11 +1,18 @@ /** * WordPress dependencies */ -import { useContext, useEffect, useState, useMemo } from '@wordpress/element'; +import { + useContext, + useEffect, + useState, + useMemo, + createInterpolateElement, +} from '@wordpress/element'; import { __experimentalSpacer as Spacer, __experimentalInputControl as InputControl, __experimentalText as Text, + __experimentalHStack as HStack, SelectControl, Spinner, Icon, @@ -14,7 +21,7 @@ import { Button, } from '@wordpress/components'; import { debounce } from '@wordpress/compose'; -import { __, _x } from '@wordpress/i18n'; +import { sprintf, __, _x } from '@wordpress/i18n'; import { search, closeSmall } from '@wordpress/icons'; /** @@ -22,7 +29,6 @@ import { search, closeSmall } from '@wordpress/icons'; */ import TabPanelLayout from './tab-panel-layout'; import { FontLibraryContext } from './context'; -import FontsGrid from './fonts-grid'; import FontCard from './font-card'; import filterFonts from './utils/filter-fonts'; import CollectionFontDetails from './collection-font-details'; @@ -48,6 +54,7 @@ function FontCollection( { slug } ) { const [ selectedFont, setSelectedFont ] = useState( null ); const [ fontsToInstall, setFontsToInstall ] = useState( [] ); + const [ page, setPage ] = useState( 1 ); const [ filters, setFilters ] = useState( {} ); const [ renderConfirmDialog, setRenderConfirmDialog ] = useState( requiresPermission && ! getGoogleFontsPermissionFromStorage() @@ -109,22 +116,34 @@ function FontCollection( { slug } ) { [ collectionFonts, filters ] ); + // NOTE: The height of the font library modal unavailable to use for rendering font family items is roughly 417px + // The height of each font family item is 61px. + const pageSize = Math.floor( ( window.innerHeight - 417 ) / 61 ); + const totalPages = Math.ceil( fonts.length / pageSize ); + const itemsStart = ( page - 1 ) * pageSize; + const itemsLimit = page * pageSize; + const items = fonts.slice( itemsStart, itemsLimit ); + const handleCategoryFilter = ( category ) => { setFilters( { ...filters, category } ); + setPage( 1 ); }; const handleUpdateSearchInput = ( value ) => { setFilters( { ...filters, search: value } ); + setPage( 1 ); }; const debouncedUpdateSearchInput = debounce( handleUpdateSearchInput, 300 ); const resetFilters = () => { setFilters( {} ); + setPage( 1 ); }; const resetSearch = () => { setFilters( { ...filters, search: '' } ); + setPage( 1 ); }; const handleUnselectFont = () => { @@ -186,6 +205,24 @@ function FontCollection( { slug } ) { resetFontsToInstall(); }; + let footerComponent = null; + if ( selectedFont ) { + footerComponent = ( + + ); + } else if ( ! renderConfirmDialog && totalPages > 1 ) { + footerComponent = ( + + ); + } + return ( - } + footer={ footerComponent } > { renderConfirmDialog && ( <> @@ -275,8 +307,8 @@ function FontCollection( { slug } ) { ) } { ! renderConfirmDialog && ! selectedFont && ( - - { fonts.map( ( font ) => ( +
+ { items.map( ( font ) => ( ) ) } - +
) }
); } -function Footer( { handleInstall, isDisabled } ) { +function PaginationFooter( { page, totalPages, setPage } ) { + return ( + + + + + { createInterpolateElement( + sprintf( + // translators: %s: Total number of pages. + _x( 'Page of %s', 'paging' ), + totalPages + ), + { + CurrenPageControl: ( + { + return { + label: i + 1, + value: i + 1, + }; + } + ) } + onChange={ ( newPage ) => + setPage( parseInt( newPage ) ) + } + size={ 'compact' } + __nextHasNoMarginBottom + /> + ), + } + ) } + + + + + ); +} + +function InstallFooter( { handleInstall, isDisabled } ) { const { isInstalling } = useContext( FontLibraryContext ); return ( diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/fonts-grid.js b/packages/edit-site/src/components/global-styles/font-library-modal/fonts-grid.js deleted file mode 100644 index 9700831a7adef1..00000000000000 --- a/packages/edit-site/src/components/global-styles/font-library-modal/fonts-grid.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * WordPress dependencies - */ -import { - __experimentalVStack as VStack, - __experimentalText as Text, - __experimentalSpacer as Spacer, -} from '@wordpress/components'; -import { useState, useEffect } from '@wordpress/element'; - -function FontsGrid( { title, children, pageSize = 32 } ) { - const [ lastItem, setLastItem ] = useState( null ); - const [ page, setPage ] = useState( 1 ); - const itemsLimit = page * pageSize; - const items = children.slice( 0, itemsLimit ); - - useEffect( () => { - if ( lastItem ) { - const observer = new window.IntersectionObserver( ( [ entry ] ) => { - if ( entry.isIntersecting ) { - setPage( ( prevPage ) => prevPage + 1 ); - } - } ); - - observer.observe( lastItem ); - - return () => observer.disconnect(); - } - }, [ lastItem ] ); - - return ( -
- - { title && ( - <> - - { title } - - - - ) } -
- { items.map( ( child, i ) => { - if ( i === itemsLimit - 1 ) { - return ( -
- { child } -
- ); - } - return
{ child }
; - } ) } -
-
-
- ); -} - -export default FontsGrid; diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js b/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js index 73ff17f25b9a66..99c99e44a43afc 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js @@ -7,6 +7,7 @@ import { privateApis as componentsPrivateApis, __experimentalHStack as HStack, __experimentalSpacer as Spacer, + __experimentalText as Text, Button, Spinner, FlexItem, @@ -17,7 +18,6 @@ import { */ import TabPanelLayout from './tab-panel-layout'; import { FontLibraryContext } from './context'; -import FontsGrid from './fonts-grid'; import LibraryFontDetails from './library-font-details'; import LibraryFontCard from './library-font-card'; import ConfirmDeleteDialog from './confirm-delete-dialog'; @@ -123,36 +123,38 @@ function InstalledFonts() { ) } { baseCustomFonts.length > 0 && ( <> - - { baseCustomFonts.map( ( font ) => ( - { - handleSelectFont( font ); - } } - /> - ) ) } - + { baseCustomFonts.map( ( font ) => ( + { + handleSelectFont( font ); + } } + /> + ) ) } ) } { baseThemeFonts.length > 0 && ( <> - - { baseThemeFonts.map( ( font ) => ( - { - handleSelectFont( font ); - } } - /> - ) ) } - + + { __( 'Theme Fonts' ) } + + + + { baseThemeFonts.map( ( font ) => ( + { + handleSelectFont( font ); + } } + /> + ) ) } ) } + ) } diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/style.scss b/packages/edit-site/src/components/global-styles/font-library-modal/style.scss index beb1ba46714ff7..544e3ed63c9883 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/style.scss +++ b/packages/edit-site/src/components/global-styles/font-library-modal/style.scss @@ -36,11 +36,8 @@ } } -.font-library-modal__fonts-grid { - .font-library-modal__fonts-grid__main { - display: flex; - flex-direction: column; - } +.font-library-modal__tabpanel-layout .components-base-control__field { + margin-bottom: 0; } .font-library-modal__font-card { From d50c836defae5f76fde56365eb4601cb2bed1dc9 Mon Sep 17 00:00:00 2001 From: Bernie Reiter <96308+ockham@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:46:02 +0100 Subject: [PATCH 02/22] Revert "Block Hooks: Set ignoredHookedBlocks metada attr upon insertion (#58553)" (#58969) This reverts commit 7449074b46bcebb4724a4ffdfbb594ae2b3fc0de. --- .../reference-guides/data/data-core-blocks.md | 48 -------- packages/block-editor/src/store/selectors.js | 10 +- packages/blocks/README.md | 14 --- packages/blocks/src/api/index.js | 1 - packages/blocks/src/api/registration.js | 15 --- packages/blocks/src/api/templates.js | 31 +---- packages/blocks/src/api/test/templates.js | 80 +------------ packages/blocks/src/store/selectors.js | 62 ---------- packages/blocks/src/store/test/selectors.js | 106 ------------------ 9 files changed, 3 insertions(+), 364 deletions(-) diff --git a/docs/reference-guides/data/data-core-blocks.md b/docs/reference-guides/data/data-core-blocks.md index a25a521931e25a..084c9c1d7a5fbc 100644 --- a/docs/reference-guides/data/data-core-blocks.md +++ b/docs/reference-guides/data/data-core-blocks.md @@ -504,54 +504,6 @@ _Returns_ - `string?`: Name of the block for handling the grouping of blocks. -### getHookedBlocks - -Returns the hooked blocks for a given anchor block. - -Given an anchor block name, returns an object whose keys are relative positions, and whose values are arrays of block names that are hooked to the anchor block at that relative position. - -_Usage_ - -```js -import { store as blocksStore } from '@wordpress/blocks'; -import { useSelect } from '@wordpress/data'; - -const ExampleComponent = () => { - const hookedBlockNames = useSelect( - ( select ) => - select( blocksStore ).getHookedBlocks( 'core/navigation' ), - [] - ); - - return ( -
    - { Object.keys( hookedBlockNames ).length && - Object.keys( hookedBlockNames ).map( ( relativePosition ) => ( -
  • - { relativePosition }> -
      - { hookedBlockNames[ relativePosition ].map( - ( hookedBlock ) => ( -
    • { hookedBlock }
    • - ) - ) } -
    -
  • - ) ) } -
- ); -}; -``` - -_Parameters_ - -- _state_ `Object`: Data state. -- _blockName_ `string`: Anchor block type name. - -_Returns_ - -- `Object`: Lists of hooked block names for each relative position. - ### getUnregisteredFallbackBlockName Returns the name of the block for handling unregistered blocks. diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 373611cd3bd8e8..3475e2b5351c80 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -10,7 +10,6 @@ import { getBlockType, getBlockTypes, getBlockVariations, - getHookedBlocks, hasBlockSupport, getPossibleBlockTransformations, parse, @@ -1937,16 +1936,9 @@ const buildBlockTypeItem = blockType.name, 'inserter' ); - - const ignoredHookedBlocks = [ - ...new Set( Object.values( getHookedBlocks( id ) ).flat() ), - ]; - return { ...blockItemBase, - initialAttributes: ignoredHookedBlocks.length - ? { metadata: { ignoredHookedBlocks } } - : {}, + initialAttributes: {}, description: blockType.description, category: blockType.category, keywords: blockType.keywords, diff --git a/packages/blocks/README.md b/packages/blocks/README.md index eda3ac629bef87..8e6fdc9d900dbb 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -234,20 +234,6 @@ _Returns_ - `?string`: Block name. -### getHookedBlocks - -Returns the hooked blocks for a given anchor block. - -Given an anchor block name, returns an object whose keys are relative positions, and whose values are arrays of block names that are hooked to the anchor block at that relative position. - -_Parameters_ - -- _name_ `string`: Anchor block name. - -_Returns_ - -- `Object`: Lists of hooked block names for each relative position. - ### getPhrasingContentSchema Undocumented declaration. diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index 8b0e389724f385..aa72979818c9c7 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -124,7 +124,6 @@ export { getBlockTypes, getBlockSupport, hasBlockSupport, - getHookedBlocks, getBlockVariations, isReusableBlock, isTemplatePart, diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 71e59949d51d17..6633adf40050c5 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -550,21 +550,6 @@ export function hasBlockSupport( nameOrType, feature, defaultSupports ) { ); } -/** - * Returns the hooked blocks for a given anchor block. - * - * Given an anchor block name, returns an object whose keys are relative positions, - * and whose values are arrays of block names that are hooked to the anchor block - * at that relative position. - * - * @param {string} name Anchor block name. - * - * @return {Object} Lists of hooked block names for each relative position. - */ -export function getHookedBlocks( name ) { - return select( blocksStore ).getHookedBlocks( name ); -} - /** * Determines whether or not the given block is a reusable block. This is a * special block type that is used to point to a global block stored via the diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index 34e6954a9ff33f..bc76218892688a 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -8,7 +8,7 @@ import { renderToString } from '@wordpress/element'; */ import { convertLegacyBlockNameAndAttributes } from './parser/convert-legacy-block'; import { createBlock } from './factory'; -import { getBlockType, getHookedBlocks } from './registration'; +import { getBlockType } from './registration'; /** * Checks whether a list of blocks matches a template by comparing the block names. @@ -115,35 +115,6 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { normalizedAttributes ); - const ignoredHookedBlocks = [ - ...new Set( - Object.values( getHookedBlocks( blockName ) ).flat() - ), - ]; - - if ( ignoredHookedBlocks.length ) { - const { metadata = {}, ...otherAttributes } = blockAttributes; - const { - ignoredHookedBlocks: ignoredHookedBlocksFromTemplate = [], - ...otherMetadata - } = metadata; - - const newIgnoredHookedBlocks = [ - ...new Set( [ - ...ignoredHookedBlocks, - ...ignoredHookedBlocksFromTemplate, - ] ), - ]; - - blockAttributes = { - metadata: { - ignoredHookedBlocks: newIgnoredHookedBlocks, - ...otherMetadata, - }, - ...otherAttributes, - }; - } - // If a Block is undefined at this point, use the core/missing block as // a placeholder for a better user experience. if ( undefined === getBlockType( blockName ) ) { diff --git a/packages/blocks/src/api/test/templates.js b/packages/blocks/src/api/test/templates.js index 8ee031aedbeefc..0a23505f0ac036 100644 --- a/packages/blocks/src/api/test/templates.js +++ b/packages/blocks/src/api/test/templates.js @@ -28,11 +28,7 @@ describe( 'templates', () => { beforeEach( () => { registerBlockType( 'core/test-block', { - attributes: { - metadata: { - type: 'object', - }, - }, + attributes: {}, save: noop, category: 'text', title: 'test block', @@ -136,80 +132,6 @@ describe( 'templates', () => { ] ); } ); - it( 'should set ignoredHookedBlocks metadata if a block has hooked blocks', () => { - registerBlockType( 'core/hooked-block', { - attributes: {}, - save: noop, - category: 'text', - title: 'hooked block', - blockHooks: { 'core/test-block': 'after' }, - } ); - - const template = [ - [ 'core/test-block' ], - [ 'core/test-block-2' ], - [ 'core/test-block-2' ], - ]; - const blockList = []; - - expect( - synchronizeBlocksWithTemplate( blockList, template ) - ).toMatchObject( [ - { - name: 'core/test-block', - attributes: { - metadata: { - ignoredHookedBlocks: [ 'core/hooked-block' ], - }, - }, - }, - { name: 'core/test-block-2' }, - { name: 'core/test-block-2' }, - ] ); - } ); - - it( 'retains previously set ignoredHookedBlocks metadata', () => { - registerBlockType( 'core/hooked-block', { - attributes: {}, - save: noop, - category: 'text', - title: 'hooked block', - blockHooks: { 'core/test-block': 'after' }, - } ); - - const template = [ - [ - 'core/test-block', - { - metadata: { - ignoredHookedBlocks: [ 'core/other-hooked-block' ], - }, - }, - ], - [ 'core/test-block-2' ], - [ 'core/test-block-2' ], - ]; - const blockList = []; - - expect( - synchronizeBlocksWithTemplate( blockList, template ) - ).toMatchObject( [ - { - name: 'core/test-block', - attributes: { - metadata: { - ignoredHookedBlocks: [ - 'core/hooked-block', - 'core/other-hooked-block', - ], - }, - }, - }, - { name: 'core/test-block-2' }, - { name: 'core/test-block-2' }, - ] ); - } ); - it( 'should create nested blocks', () => { const template = [ [ 'core/test-block', {}, [ [ 'core/test-block-2' ] ] ], diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 9eda135d0d6999..b2b8ab8106f097 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -106,68 +106,6 @@ export function getBlockType( state, name ) { return state.blockTypes[ name ]; } -/** - * Returns the hooked blocks for a given anchor block. - * - * Given an anchor block name, returns an object whose keys are relative positions, - * and whose values are arrays of block names that are hooked to the anchor block - * at that relative position. - * - * @param {Object} state Data state. - * @param {string} blockName Anchor block type name. - * - * @example - * ```js - * import { store as blocksStore } from '@wordpress/blocks'; - * import { useSelect } from '@wordpress/data'; - * - * const ExampleComponent = () => { - * const hookedBlockNames = useSelect( ( select ) => - * select( blocksStore ).getHookedBlocks( 'core/navigation' ), - * [] - * ); - * - * return ( - *
    - * { Object.keys( hookedBlockNames ).length && - * Object.keys( hookedBlockNames ).map( ( relativePosition ) => ( - *
  • { relativePosition }> - *
      - * { hookedBlockNames[ relativePosition ].map( ( hookedBlock ) => ( - *
    • { hookedBlock }
    • - * ) ) } - *
    - *
  • - * ) ) } - *
- * ); - * }; - * ``` - * - * @return {Object} Lists of hooked block names for each relative position. - */ -export const getHookedBlocks = createSelector( - ( state, blockName ) => { - const hookedBlockTypes = getBlockTypes( state ).filter( - ( { blockHooks } ) => blockHooks && blockName in blockHooks - ); - - let hookedBlocks = {}; - for ( const blockType of hookedBlockTypes ) { - const relativePosition = blockType.blockHooks[ blockName ]; - hookedBlocks = { - ...hookedBlocks, - [ relativePosition ]: [ - ...( hookedBlocks[ relativePosition ] ?? [] ), - blockType.name, - ], - }; - } - return hookedBlocks; - }, - ( state ) => [ state.blockTypes ] -); - /** * Returns block styles by block name. * diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index 0dcdde3c07bf10..1fda11d72311a3 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -12,7 +12,6 @@ import { getBlockVariations, getDefaultBlockVariation, getGroupingBlockName, - getHookedBlocks, isMatchingSearchTerm, getCategories, getActiveBlockVariation, @@ -229,111 +228,6 @@ describe( 'selectors', () => { } ); } ); - describe( 'getHookedBlocks', () => { - it( 'should return an empty object if state is empty', () => { - const state = { - blockTypes: {}, - }; - - expect( getHookedBlocks( state, 'anchor' ) ).toEqual( {} ); - } ); - - it( 'should return an empty object if the anchor block is not found', () => { - const state = { - blockTypes: { - anchor: { - name: 'anchor', - }, - hookedBlock: { - name: 'hookedBlock', - blockHooks: { - anchor: 'after', - }, - }, - }, - }; - - expect( getHookedBlocks( state, 'otherAnchor' ) ).toEqual( {} ); - } ); - - it( "should return the anchor block name even if the anchor block doesn't exist", () => { - const state = { - blockTypes: { - hookedBlock: { - name: 'hookedBlock', - blockHooks: { - anchor: 'after', - }, - }, - }, - }; - - expect( getHookedBlocks( state, 'anchor' ) ).toEqual( { - after: [ 'hookedBlock' ], - } ); - } ); - - it( 'should return an array with the hooked block names', () => { - const state = { - blockTypes: { - anchor: { - name: 'anchor', - }, - hookedBlock1: { - name: 'hookedBlock1', - blockHooks: { - anchor: 'after', - }, - }, - hookedBlock2: { - name: 'hookedBlock2', - blockHooks: { - anchor: 'before', - }, - }, - }, - }; - - expect( getHookedBlocks( state, 'anchor' ) ).toEqual( { - after: [ 'hookedBlock1' ], - before: [ 'hookedBlock2' ], - } ); - } ); - - it( 'should return an array with the hooked block names, even if multiple blocks are in the same relative position', () => { - const state = { - blockTypes: { - anchor: { - name: 'anchor', - }, - hookedBlock1: { - name: 'hookedBlock1', - blockHooks: { - anchor: 'after', - }, - }, - hookedBlock2: { - name: 'hookedBlock2', - blockHooks: { - anchor: 'before', - }, - }, - hookedBlock3: { - name: 'hookedBlock3', - blockHooks: { - anchor: 'after', - }, - }, - }, - }; - - expect( getHookedBlocks( state, 'anchor' ) ).toEqual( { - after: [ 'hookedBlock1', 'hookedBlock3' ], - before: [ 'hookedBlock2' ], - } ); - } ); - } ); - describe( 'Testing block variations selectors', () => { const blockName = 'block/name'; const createBlockVariationsState = ( variations ) => { From 16f79482e957bde93b51f07c208cd9f418a24f0e Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Tue, 13 Feb 2024 17:49:02 +0200 Subject: [PATCH 03/22] DataViews: Fix patterns, templates and template parts pagination `z-index` (#58965) Co-authored-by: ntsekouras Co-authored-by: t-hamano Co-authored-by: jameskoster Co-authored-by: annezazu Co-authored-by: miksansegundo --- packages/base-styles/_z-index.scss | 3 ++- packages/edit-site/src/components/page-patterns/style.scss | 4 ++++ .../src/components/page-templates-template-parts/index.js | 1 + .../src/components/page-templates-template-parts/style.scss | 6 ++++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index e6d8d12769e701..ff21d1d8df8f35 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -190,7 +190,8 @@ $z-layers: ( ".edit-site-page-header": 2, ".edit-site-page-content": 1, ".edit-site-patterns__header": 2, - ".edit-site-patterns__grid-pagination": 2, + ".edit-site-patterns__dataviews-list-pagination": 2, + ".edit-site-templates__dataviews-list-pagination": 2, ".edit-site-layout__canvas-container": 2, ".edit-site-layout__sidebar": 1, ".edit-site-layout__canvas-container.is-resizing::after": 100, diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index e5bd44956b2629..0be55756d1609d 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -283,6 +283,10 @@ text-overflow: ellipsis; color: inherit; } + + .dataviews-pagination { + z-index: z-index(".edit-site-patterns__dataviews-list-pagination"); + } } .dataviews-action-modal__duplicate-pattern { diff --git a/packages/edit-site/src/components/page-templates-template-parts/index.js b/packages/edit-site/src/components/page-templates-template-parts/index.js index 514ff148071955..9089d31cd66e0a 100644 --- a/packages/edit-site/src/components/page-templates-template-parts/index.js +++ b/packages/edit-site/src/components/page-templates-template-parts/index.js @@ -429,6 +429,7 @@ export default function PageTemplatesTemplateParts( { postType } ) { return ( Date: Tue, 13 Feb 2024 17:49:07 +0100 Subject: [PATCH 04/22] Block Bindings: Fix disable bindings editing when source is undefined (#58961) * Disable editing if source is undefined * Add tests when source is undefined --- .../src/components/rich-text/index.js | 17 +- packages/block-library/src/button/edit.js | 8 +- packages/block-library/src/image/edit.js | 8 +- packages/block-library/src/image/image.js | 21 +- .../editor/various/block-bindings.spec.js | 453 +++++++++++++++++- 5 files changed, 480 insertions(+), 27 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index f45cc618d4547e..bac2559ffbba9b 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -156,12 +156,19 @@ export function RichTextWrapper( for ( const [ attribute, args ] of Object.entries( blockBindings ) ) { - // If any of the attributes with source "rich-text" is part of the bindings, - // has a source with `lockAttributesEditing`, disable it. if ( - blockTypeAttributes?.[ attribute ]?.source === - 'rich-text' && - getBlockBindingsSource( args.source )?.lockAttributesEditing + blockTypeAttributes?.[ attribute ]?.source !== 'rich-text' + ) { + break; + } + + // If the source is not defined, or if its value of `lockAttributesEditing` is `true`, disable it. + const blockBindingsSource = getBlockBindingsSource( + args.source + ); + if ( + ! blockBindingsSource || + blockBindingsSource.lockAttributesEditing ) { shouldDisableEditing = true; break; diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index e01898ca00dec4..ff90cdd1bf64c0 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -238,15 +238,15 @@ function ButtonEdit( props ) { return {}; } - const { getBlockBindingsSource } = unlock( + const blockBindingsSource = unlock( select( blockEditorStore ) - ); + ).getBlockBindingsSource( metadata?.bindings?.url?.source ); return { lockUrlControls: !! metadata?.bindings?.url && - getBlockBindingsSource( metadata?.bindings?.url?.source ) - ?.lockAttributesEditing, + ( ! blockBindingsSource || + blockBindingsSource?.lockAttributesEditing ), }; }, [ isSelected ] diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 61d023e4e580a1..86970b588ff03d 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -341,15 +341,15 @@ export function ImageEdit( { return {}; } - const { getBlockBindingsSource } = unlock( + const blockBindingsSource = unlock( select( blockEditorStore ) - ); + ).getBlockBindingsSource( metadata?.bindings?.url?.source ); return { lockUrlControls: !! metadata?.bindings?.url && - getBlockBindingsSource( metadata?.bindings?.url?.source ) - ?.lockAttributesEditing, + ( ! blockBindingsSource || + blockBindingsSource?.lockAttributesEditing ), }; }, [ isSingleSelected ] diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 62184e6522adb9..a0d481681ee934 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -426,23 +426,32 @@ export default function Image( { } = metadata?.bindings || {}; const hasParentPattern = getBlockParentsByBlockName( clientId, 'core/block' ).length > 0; + const urlBindingSource = getBlockBindingsSource( + urlBinding?.source + ); + const altBindingSource = getBlockBindingsSource( + altBinding?.source + ); + const titleBindingSource = getBlockBindingsSource( + titleBinding?.source + ); return { lockUrlControls: !! urlBinding && - getBlockBindingsSource( urlBinding?.source ) - ?.lockAttributesEditing, + ( ! urlBindingSource || + urlBindingSource?.lockAttributesEditing ), lockHrefControls: // Disable editing the link of the URL if the image is inside a pattern instance. // This is a temporary solution until we support overriding the link on the frontend. hasParentPattern, lockAltControls: !! altBinding && - getBlockBindingsSource( altBinding?.source ) - ?.lockAttributesEditing, + ( ! altBindingSource || + altBindingSource?.lockAttributesEditing ), lockTitleControls: !! titleBinding && - getBlockBindingsSource( titleBinding?.source ) - ?.lockAttributesEditing, + ( ! titleBindingSource || + titleBindingSource?.lockAttributesEditing ), }; }, [ clientId, isSingleSelected, metadata?.bindings ] diff --git a/test/e2e/specs/editor/various/block-bindings.spec.js b/test/e2e/specs/editor/various/block-bindings.spec.js index 9392058ce54d6a..6d0aa11563730d 100644 --- a/test/e2e/specs/editor/various/block-bindings.spec.js +++ b/test/e2e/specs/editor/various/block-bindings.spec.js @@ -72,7 +72,7 @@ test.describe( 'Block bindings', () => { ); } ); - test( 'Should lock the appropriate controls', async ( { + test( 'Should lock the appropriate controls with a registered source', async ( { editor, page, } ) => { @@ -117,6 +117,52 @@ test.describe( 'Block bindings', () => { 'false' ); } ); + + test( 'Should lock the appropriate controls when source is not defined', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'paragraph default content', + metadata: { + bindings: { + content: { + source: 'plugin/undefined-source', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + await paragraphBlock.click(); + + // Alignment controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Align text' } ) + ).toBeVisible(); + + // Format controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeHidden(); + + // Paragraph is not editable. + await expect( paragraphBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + } ); } ); test.describe( 'Heading', () => { @@ -143,7 +189,7 @@ test.describe( 'Block bindings', () => { await expect( headingBlock ).toHaveText( 'text_custom_field' ); } ); - test( 'Should lock the appropriate controls', async ( { + test( 'Should lock the appropriate controls with a registered source', async ( { editor, page, } ) => { @@ -188,6 +234,52 @@ test.describe( 'Block bindings', () => { 'false' ); } ); + + test( 'Should lock the appropriate controls when source is not defined', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'heading default content', + metadata: { + bindings: { + content: { + source: 'plugin/undefined-source', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const headingBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Heading', + } ); + await headingBlock.click(); + + // Alignment controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Align text' } ) + ).toBeVisible(); + + // Format controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeHidden(); + + // Heading is not editable. + await expect( headingBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + } ); } ); test.describe( 'Button', () => { @@ -221,7 +313,7 @@ test.describe( 'Block bindings', () => { await expect( buttonBlock ).toHaveText( 'text_custom_field' ); } ); - test( 'Should lock text controls when text is bound', async ( { + test( 'Should lock text controls when text is bound to a registered source', async ( { editor, page, } ) => { @@ -283,7 +375,69 @@ test.describe( 'Block bindings', () => { ).toBeVisible(); } ); - test( 'Should lock url controls when url is bound', async ( { + test( 'Should lock text controls when text is bound to an undefined source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + text: { + source: 'plugin/undefined-source', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + const buttonBlock = editor.canvas + .getByRole( 'document', { + name: 'Block: Button', + exact: true, + } ) + .getByRole( 'textbox' ); + await buttonBlock.click(); + + // Alignment controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Align text' } ) + ).toBeVisible(); + + // Format controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeHidden(); + + // Button is not editable. + await expect( buttonBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + + // Link controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Unlink' } ) + ).toBeVisible(); + } ); + + test( 'Should lock url controls when url is bound to a registered source', async ( { editor, page, } ) => { @@ -343,6 +497,66 @@ test.describe( 'Block bindings', () => { ).toBeHidden(); } ); + test( 'Should lock url controls when url is bound to an undefined source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + url: { + source: 'plugin/undefined-source', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + const buttonBlock = editor.canvas + .getByRole( 'document', { + name: 'Block: Button', + exact: true, + } ) + .getByRole( 'textbox' ); + await buttonBlock.click(); + + // Format controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeVisible(); + + // Button is editable. + await expect( buttonBlock ).toHaveAttribute( + 'contenteditable', + 'true' + ); + + // Link controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Link' } ) + ).toBeHidden(); + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Unlink' } ) + ).toBeHidden(); + } ); + test( 'Should lock url and text controls when both are bound', async ( { editor, page, @@ -429,7 +643,7 @@ test.describe( 'Block bindings', () => { ).toBeVisible(); } ); - test( 'Should NOT show the upload form when url is bound', async ( { + test( 'Should NOT show the upload form when url is bound to a registered source', async ( { editor, } ) => { await editor.insertBlock( { @@ -457,7 +671,35 @@ test.describe( 'Block bindings', () => { ).toBeHidden(); } ); - test( 'Should lock url controls when url is bound', async ( { + test( 'Should NOT show the upload form when url is bound to an undefined source', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + url: { + source: 'plugin/undefined-source', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + await expect( + imageBlock.getByRole( 'button', { name: 'Upload' } ) + ).toBeHidden(); + } ); + + test( 'Should lock url controls when url is bound to a registered source', async ( { editor, page, } ) => { @@ -526,7 +768,76 @@ test.describe( 'Block bindings', () => { expect( titleValue ).toBe( 'default title value' ); } ); - test( 'Should disable alt textarea when alt is bound', async ( { + test( 'Should lock url controls when url is bound to an undefined source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + url: { + source: 'plugin/undefined-source', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + + // Replace controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Replace', + } ) + ).toBeHidden(); + + // Image placeholder doesn't show the upload button. + await expect( + imageBlock.getByRole( 'button', { name: 'Upload' } ) + ).toBeHidden(); + + // Alt textarea is enabled and with the original value. + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + ).toBeEnabled(); + const altValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + .inputValue(); + expect( altValue ).toBe( 'default alt value' ); + + // Title input is enabled and with the original value. + await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByRole( 'button', { name: 'Advanced' } ) + .click(); + + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + ).toBeEnabled(); + const titleValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + .inputValue(); + expect( titleValue ).toBe( 'default title value' ); + } ); + + test( 'Should disable alt textarea when alt is bound to a registered source', async ( { editor, page, } ) => { @@ -589,7 +900,70 @@ test.describe( 'Block bindings', () => { expect( titleValue ).toBe( 'default title value' ); } ); - test( 'Should disable title input when title is bound', async ( { + test( 'Should disable alt textarea when alt is bound to an undefined source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + alt: { + source: 'plguin/undefined-source', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + + // Replace controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Replace', + } ) + ).toBeVisible(); + + // Alt textarea is disabled and with the custom field value. + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + ).toBeDisabled(); + const altValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + .inputValue(); + expect( altValue ).toBe( 'default alt value' ); + + // Title input is enabled and with the original value. + await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByRole( 'button', { name: 'Advanced' } ) + .click(); + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + ).toBeEnabled(); + const titleValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + .inputValue(); + expect( titleValue ).toBe( 'default title value' ); + } ); + + test( 'Should disable title input when title is bound to a registered source', async ( { editor, page, } ) => { @@ -652,6 +1026,69 @@ test.describe( 'Block bindings', () => { expect( titleValue ).toBe( 'text_custom_field' ); } ); + test( 'Should disable title input when title is bound to an undefined source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + title: { + source: 'plugin/undefined-source', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + + // Replace controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Replace', + } ) + ).toBeVisible(); + + // Alt textarea is enabled and with the original value. + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + ).toBeEnabled(); + const altValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + .inputValue(); + expect( altValue ).toBe( 'default alt value' ); + + // Title input is disabled and with the custom field value. + await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByRole( 'button', { name: 'Advanced' } ) + .click(); + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + ).toBeDisabled(); + const titleValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + .inputValue(); + expect( titleValue ).toBe( 'default title value' ); + } ); + test( 'Multiple bindings should lock the appropriate controls', async ( { editor, page, From 942cc3e2c2e12a84886bccb77f12df759c2863f7 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Tue, 13 Feb 2024 23:55:58 +0200 Subject: [PATCH 05/22] Patterns: Fix pattern categories on import (#58926) Co-authored-by: ntsekouras Co-authored-by: aaronrobertshaw Co-authored-by: glendaviesnz Co-authored-by: bph Co-authored-by: hanneslsm Co-authored-by: colorful-tones Co-authored-by: annezazu --- .../src/components/add-new-pattern/index.js | 38 +++++--- .../src/components/create-pattern-modal.js | 84 +---------------- packages/patterns/src/private-apis.js | 2 + packages/patterns/src/private-hooks.js | 91 +++++++++++++++++++ 4 files changed, 125 insertions(+), 90 deletions(-) create mode 100644 packages/patterns/src/private-hooks.js diff --git a/packages/edit-site/src/components/add-new-pattern/index.js b/packages/edit-site/src/components/add-new-pattern/index.js index 014ac6165aaef2..0d25ff67bf4482 100644 --- a/packages/edit-site/src/components/add-new-pattern/index.js +++ b/packages/edit-site/src/components/add-new-pattern/index.js @@ -25,10 +25,11 @@ import { PATTERN_DEFAULT_CATEGORY, TEMPLATE_PART_POST_TYPE, } from '../../utils/constants'; -import usePatternCategories from '../sidebar-navigation-screen-patterns/use-pattern-categories'; const { useHistory, useLocation } = unlock( routerPrivateApis ); -const { CreatePatternModal } = unlock( editPatternsPrivateApis ); +const { CreatePatternModal, useAddPatternCategory } = unlock( + editPatternsPrivateApis +); export default function AddNewPattern() { const history = useHistory(); @@ -43,7 +44,6 @@ export default function AddNewPattern() { const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); const patternUploadInputRef = useRef(); - const { patternCategories } = usePatternCategories(); function handleCreatePattern( { pattern, categoryId } ) { setShowPatternModal( false ); @@ -97,6 +97,7 @@ export default function AddNewPattern() { title: __( 'Import pattern from JSON' ), } ); + const { categoryMap, findOrCreateTerm } = useAddPatternCategory(); return ( <> - category.name === params.categoryId - )?.id; + let currentCategoryId; + // When we're not handling template parts, we should + // add or create the proper pattern category. + if ( params.categoryType !== TEMPLATE_PART_POST_TYPE ) { + const currentCategory = categoryMap + .values() + .find( + ( term ) => term.name === params.categoryId + ); + if ( !! currentCategory ) { + currentCategoryId = + currentCategory.id || + ( await findOrCreateTerm( + currentCategory.label + ) ); + } + } const pattern = await createPatternFromFile( file, currentCategoryId @@ -146,8 +158,12 @@ export default function AddNewPattern() { ); // Navigate to the All patterns category for the newly created pattern - // if we're not on that page already. - if ( ! currentCategoryId ) { + // if we're not on that page already and if we're not in the `my-patterns` + // category. + if ( + ! currentCategoryId && + params.categoryId !== 'my-patterns' + ) { history.push( { path: `/patterns`, categoryType: PATTERN_TYPES.theme, diff --git a/packages/patterns/src/components/create-pattern-modal.js b/packages/patterns/src/components/create-pattern-modal.js index 137c14222ced34..9576e50309e237 100644 --- a/packages/patterns/src/components/create-pattern-modal.js +++ b/packages/patterns/src/components/create-pattern-modal.js @@ -10,21 +10,17 @@ import { ToggleControl, } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; -import { useState, useMemo } from '@wordpress/element'; -import { useDispatch, useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; -import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies */ import { PATTERN_DEFAULT_CATEGORY, PATTERN_SYNC_TYPES } from '../constants'; - -/** - * Internal dependencies - */ import { store as patternsStore } from '../store'; -import CategorySelector, { CATEGORY_SLUG } from './category-selector'; +import CategorySelector from './category-selector'; +import { useAddPatternCategory } from '../private-hooks'; import { unlock } from '../lock-unlock'; export default function CreatePatternModal( { @@ -59,47 +55,9 @@ export function CreatePatternModalContents( { const [ isSaving, setIsSaving ] = useState( false ); const { createPattern } = unlock( useDispatch( patternsStore ) ); - const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore ); const { createErrorNotice } = useDispatch( noticesStore ); - const { corePatternCategories, userPatternCategories } = useSelect( - ( select ) => { - const { getUserPatternCategories, getBlockPatternCategories } = - select( coreStore ); - - return { - corePatternCategories: getBlockPatternCategories(), - userPatternCategories: getUserPatternCategories(), - }; - } - ); - - const categoryMap = useMemo( () => { - // Merge the user and core pattern categories and remove any duplicates. - const uniqueCategories = new Map(); - userPatternCategories.forEach( ( category ) => { - uniqueCategories.set( category.label.toLowerCase(), { - label: category.label, - name: category.name, - id: category.id, - } ); - } ); - - corePatternCategories.forEach( ( category ) => { - if ( - ! uniqueCategories.has( category.label.toLowerCase() ) && - // There are two core categories with `Post` label so explicitly remove the one with - // the `query` slug to avoid any confusion. - category.name !== 'query' - ) { - uniqueCategories.set( category.label.toLowerCase(), { - label: category.label, - name: category.name, - } ); - } - } ); - return uniqueCategories; - }, [ userPatternCategories, corePatternCategories ] ); + const { categoryMap, findOrCreateTerm } = useAddPatternCategory(); async function onCreate( patternTitle, sync ) { if ( ! title || isSaving ) { @@ -137,38 +95,6 @@ export function CreatePatternModalContents( { } } - /** - * @param {string} term - * @return {Promise} The pattern category id. - */ - async function findOrCreateTerm( term ) { - try { - const existingTerm = categoryMap.get( term.toLowerCase() ); - if ( existingTerm && existingTerm.id ) { - return existingTerm.id; - } - // If we have an existing core category we need to match the new user category to the - // correct slug rather than autogenerating it to prevent duplicates, eg. the core `Headers` - // category uses the singular `header` as the slug. - const termData = existingTerm - ? { name: existingTerm.label, slug: existingTerm.name } - : { name: term }; - const newTerm = await saveEntityRecord( - 'taxonomy', - CATEGORY_SLUG, - termData, - { throwOnError: true } - ); - invalidateResolution( 'getUserPatternCategories' ); - return newTerm.id; - } catch ( error ) { - if ( error.code !== 'term_exists' ) { - throw error; - } - - return error.data.term_id; - } - } return (
{ diff --git a/packages/patterns/src/private-apis.js b/packages/patterns/src/private-apis.js index 099e4ae8ffed4c..a5fbddb62fd62c 100644 --- a/packages/patterns/src/private-apis.js +++ b/packages/patterns/src/private-apis.js @@ -15,6 +15,7 @@ import PatternsMenuItems from './components'; import RenamePatternCategoryModal from './components/rename-pattern-category-modal'; import PartialSyncingControls from './components/partial-syncing-controls'; import ResetOverridesControl from './components/reset-overrides-control'; +import { useAddPatternCategory } from './private-hooks'; import { PATTERN_TYPES, PATTERN_DEFAULT_CATEGORY, @@ -35,6 +36,7 @@ lock( privateApis, { RenamePatternCategoryModal, PartialSyncingControls, ResetOverridesControl, + useAddPatternCategory, PATTERN_TYPES, PATTERN_DEFAULT_CATEGORY, PATTERN_USER_CATEGORY, diff --git a/packages/patterns/src/private-hooks.js b/packages/patterns/src/private-hooks.js new file mode 100644 index 00000000000000..7dee37222fbbda --- /dev/null +++ b/packages/patterns/src/private-hooks.js @@ -0,0 +1,91 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { CATEGORY_SLUG } from './components/category-selector'; + +/** + * Helper hook that creates a Map with the core and user patterns categories + * and removes any duplicates. It's used when we need to create new user + * categories when creating or importing patterns. + * This hook also provides a function to find or create a pattern category. + * + * @return {Object} The merged categories map and the callback function to find or create a category. + */ +export function useAddPatternCategory() { + const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore ); + const { corePatternCategories, userPatternCategories } = useSelect( + ( select ) => { + const { getUserPatternCategories, getBlockPatternCategories } = + select( coreStore ); + + return { + corePatternCategories: getBlockPatternCategories(), + userPatternCategories: getUserPatternCategories(), + }; + }, + [] + ); + const categoryMap = useMemo( () => { + // Merge the user and core pattern categories and remove any duplicates. + const uniqueCategories = new Map(); + userPatternCategories.forEach( ( category ) => { + uniqueCategories.set( category.label.toLowerCase(), { + label: category.label, + name: category.name, + id: category.id, + } ); + } ); + + corePatternCategories.forEach( ( category ) => { + if ( + ! uniqueCategories.has( category.label.toLowerCase() ) && + // There are two core categories with `Post` label so explicitly remove the one with + // the `query` slug to avoid any confusion. + category.name !== 'query' + ) { + uniqueCategories.set( category.label.toLowerCase(), { + label: category.label, + name: category.name, + } ); + } + } ); + return uniqueCategories; + }, [ userPatternCategories, corePatternCategories ] ); + + async function findOrCreateTerm( term ) { + try { + const existingTerm = categoryMap.get( term.toLowerCase() ); + if ( existingTerm?.id ) { + return existingTerm.id; + } + // If we have an existing core category we need to match the new user category to the + // correct slug rather than autogenerating it to prevent duplicates, eg. the core `Headers` + // category uses the singular `header` as the slug. + const termData = existingTerm + ? { name: existingTerm.label, slug: existingTerm.name } + : { name: term }; + const newTerm = await saveEntityRecord( + 'taxonomy', + CATEGORY_SLUG, + termData, + { throwOnError: true } + ); + invalidateResolution( 'getUserPatternCategories' ); + return newTerm.id; + } catch ( error ) { + if ( error.code !== 'term_exists' ) { + throw error; + } + return error.data.term_id; + } + } + + return { categoryMap, findOrCreateTerm }; +} From 0ef370b794bb5f8da5c0ad411e10b5336e9f03e5 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Tue, 13 Feb 2024 22:14:12 +0000 Subject: [PATCH 06/22] Shadows: Don't assume that core provides defaults (#58973) Co-authored-by: vcanales Co-authored-by: scruffian --- .../src/components/global-styles/shadow-panel-components.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/global-styles/shadow-panel-components.js b/packages/block-editor/src/components/global-styles/shadow-panel-components.js index 6e4e3a15b184d8..8c9ba795bc17ba 100644 --- a/packages/block-editor/src/components/global-styles/shadow-panel-components.js +++ b/packages/block-editor/src/components/global-styles/shadow-panel-components.js @@ -19,13 +19,13 @@ import { shadow as shadowIcon, Icon, check } from '@wordpress/icons'; import classNames from 'classnames'; export function ShadowPopoverContainer( { shadow, onShadowChange, settings } ) { - const defaultShadows = settings?.shadow?.presets?.default; - const themeShadows = settings?.shadow?.presets?.theme; + const defaultShadows = settings?.shadow?.presets?.default || []; + const themeShadows = settings?.shadow?.presets?.theme || []; const defaultPresetsEnabled = settings?.shadow?.defaultPresets; const shadows = [ ...( defaultPresetsEnabled ? defaultShadows : [] ), - ...( themeShadows || [] ), + ...themeShadows, ]; return ( From 0f3e9fbddb587a5c7d7294bb933789cfd0c6cf32 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 14 Feb 2024 07:17:03 +0400 Subject: [PATCH 07/22] Inserter: Don't select the closest block with editing mode set as disabled (#58971) Co-authored-by: Mamaduka Co-authored-by: noisysocks Co-authored-by: dernin --- .../src/components/block-tools/insertion-point.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-tools/insertion-point.js b/packages/block-editor/src/components/block-tools/insertion-point.js index 19ad39caca336a..bd4c8ea5fb371c 100644 --- a/packages/block-editor/src/components/block-tools/insertion-point.js +++ b/packages/block-editor/src/components/block-tools/insertion-point.js @@ -81,11 +81,16 @@ function InbetweenInsertionPointPopover( { isInserterShown: insertionPoint?.__unstableWithInserter, }; }, [] ); + const { getBlockEditingMode } = useSelect( blockEditorStore ); const disableMotion = useReducedMotion(); function onClick( event ) { - if ( event.target === ref.current && nextClientId ) { + if ( + event.target === ref.current && + nextClientId && + getBlockEditingMode( nextClientId ) !== 'disabled' + ) { selectBlock( nextClientId, -1 ); } } From 79dffd9133cafe14407bb74fb98a6820ecf61f39 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Wed, 14 Feb 2024 09:48:53 +0100 Subject: [PATCH 08/22] Make the custom CSS validation error message accessible. (#56690) Co-authored-by: Alex Stine --- .../components/global-styles/advanced-panel.js | 18 ++++++------------ .../src/components/global-styles/style.scss | 10 ---------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/packages/block-editor/src/components/global-styles/advanced-panel.js b/packages/block-editor/src/components/global-styles/advanced-panel.js index af43552c0a3eba..1ad59451d1468a 100644 --- a/packages/block-editor/src/components/global-styles/advanced-panel.js +++ b/packages/block-editor/src/components/global-styles/advanced-panel.js @@ -3,12 +3,11 @@ */ import { TextareaControl, - Tooltip, + Notice, __experimentalVStack as VStack, } from '@wordpress/components'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Icon, info } from '@wordpress/icons'; /** * Internal dependencies @@ -58,6 +57,11 @@ export default function AdvancedPanel( { return ( + { cssError && ( + setCSSError( null ) }> + { cssError } + + ) } - { cssError && ( - -
- -
-
- ) }
); } diff --git a/packages/block-editor/src/components/global-styles/style.scss b/packages/block-editor/src/components/global-styles/style.scss index 010c5faaefff44..d2ba88f9f31e00 100644 --- a/packages/block-editor/src/components/global-styles/style.scss +++ b/packages/block-editor/src/components/global-styles/style.scss @@ -47,13 +47,3 @@ /*rtl:ignore*/ direction: ltr; } - -.block-editor-global-styles-advanced-panel__custom-css-validation-wrapper { - position: absolute; - bottom: $grid-unit-20; - right: $grid-unit * 3; -} - -.block-editor-global-styles-advanced-panel__custom-css-validation-icon { - fill: $alert-red; -} From a01f6a7daf697c7a3f489a9b02fc06f420c9f8cf Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 14 Feb 2024 15:07:45 +0400 Subject: [PATCH 09/22] Inserter: Fix title condition for media tab previews (#58993) Unlinked contributors: nbcsteveb. Co-authored-by: Mamaduka Co-authored-by: ntsekouras --- .../src/components/inserter/media-tab/media-preview.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/inserter/media-tab/media-preview.js b/packages/block-editor/src/components/inserter/media-tab/media-preview.js index 9efed229f0adf2..d3b221910eae22 100644 --- a/packages/block-editor/src/components/inserter/media-tab/media-preview.js +++ b/packages/block-editor/src/components/inserter/media-tab/media-preview.js @@ -195,7 +195,12 @@ export function MediaPreview( { media, onClick, category } ) { createSuccessNotice, ] ); - const title = media.title?.rendered || media.title; + + const title = + typeof media.title === 'string' + ? media.title + : media.title?.rendered || __( 'no title' ); + let truncatedTitle; if ( title.length > MAXIMUM_TITLE_LENGTH ) { const omission = '...'; From 1e7029886cd861df9265c6442520e9893eb9a625 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Wed, 14 Feb 2024 13:44:35 +0200 Subject: [PATCH 10/22] DataViews: Add loading/no results message in grid view (#59002) Co-authored-by: ntsekouras Co-authored-by: oandregal Co-authored-by: annezazu Co-authored-by: t-hamano --- packages/dataviews/src/view-grid.js | 64 ++++++++++++------- .../components/page-patterns/use-patterns.js | 18 ++++-- 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/packages/dataviews/src/view-grid.js b/packages/dataviews/src/view-grid.js index 14024163878d1d..44ab1822a60750 100644 --- a/packages/dataviews/src/view-grid.js +++ b/packages/dataviews/src/view-grid.js @@ -12,6 +12,7 @@ import { __experimentalVStack as VStack, Tooltip, } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; import { useAsyncList } from '@wordpress/compose'; import { useState } from '@wordpress/element'; @@ -128,6 +129,7 @@ export default function ViewGrid( { fields, view, actions, + isLoading, getItemId, deferredRendering, selection, @@ -148,29 +150,45 @@ export default function ViewGrid( { ); const shownData = useAsyncList( data, { step: 3 } ); const usedData = deferredRendering ? shownData : data; + const hasData = !! usedData?.length; return ( - - { usedData.map( ( item ) => { - return ( - - ); - } ) } - + <> + { hasData && ( + + { usedData.map( ( item ) => { + return ( + + ); + } ) } + + ) } + { ! hasData && ( +
+

{ isLoading ? __( 'Loading…' ) : __( 'No results' ) }

+
+ ) } + ); } diff --git a/packages/edit-site/src/components/page-patterns/use-patterns.js b/packages/edit-site/src/components/page-patterns/use-patterns.js index a0b82247c85a6d..b099b70b36fe1a 100644 --- a/packages/edit-site/src/components/page-patterns/use-patterns.js +++ b/packages/edit-site/src/components/page-patterns/use-patterns.js @@ -111,6 +111,7 @@ const selectTemplatePartsAsPatterns = createSelector( const selectThemePatterns = createSelector( ( select ) => { const { getSettings } = unlock( select( editSiteStore ) ); + const { getIsResolving } = select( coreStore ); const settings = getSettings(); const blockPatterns = settings.__experimentalAdditionalBlockPatterns ?? @@ -136,19 +137,23 @@ const selectThemePatterns = createSelector( __unstableSkipMigrationLogs: true, } ), } ) ); - - return { patterns, isResolving: false }; + return { patterns, isResolving: getIsResolving( 'getBlockPatterns' ) }; }, ( select ) => [ select( coreStore ).getBlockPatterns(), + select( coreStore ).getIsResolving( 'getBlockPatterns' ), unlock( select( editSiteStore ) ).getSettings(), ] ); const selectPatterns = createSelector( ( select, categoryId, syncStatus, search = '' ) => { - const { patterns: themePatterns } = selectThemePatterns( select ); - const { patterns: userPatterns } = selectUserPatterns( select ); + const { + patterns: themePatterns, + isResolving: isResolvingThemePatterns, + } = selectThemePatterns( select ); + const { patterns: userPatterns, isResolving: isResolvingUserPatterns } = + selectUserPatterns( select ); let patterns = [ ...( themePatterns || [] ), @@ -176,7 +181,10 @@ const selectPatterns = createSelector( hasCategory: ( item ) => ! item.hasOwnProperty( 'categories' ), } ); } - return { patterns, isResolving: false }; + return { + patterns, + isResolving: isResolvingThemePatterns || isResolvingUserPatterns, + }; }, ( select ) => [ selectThemePatterns( select ), From 4b25a8869ecc7021b4d9c7e088a04fe4cfdba658 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:16:36 +0100 Subject: [PATCH 11/22] Block editor: pass patterns selector as setting (#58661) Co-authored-by: ellatrix Co-authored-by: jsnajdr Co-authored-by: Mamaduka --- packages/block-editor/src/private-apis.js | 2 ++ packages/block-editor/src/store/index.js | 2 -- .../block-editor/src/store/private-keys.js | 1 + .../src/store/private-selectors.js | 11 +++----- packages/block-editor/src/store/reducer.js | 10 ------- packages/block-editor/src/store/resolvers.js | 17 ------------ packages/block-editor/src/store/selectors.js | 12 ++++----- packages/block-editor/src/store/utils.js | 9 +++++-- packages/core-data/src/private-selectors.ts | 27 +++++++++++++++++++ .../provider/use-block-editor-settings.js | 18 +++++-------- 10 files changed, 53 insertions(+), 56 deletions(-) create mode 100644 packages/block-editor/src/store/private-keys.js delete mode 100644 packages/block-editor/src/store/resolvers.js diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index 0ba05e88a0f9b1..220aa5f4127270 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -26,6 +26,7 @@ import { usesContextKey } from './components/rich-text/format-edit'; import { ExperimentalBlockCanvas } from './components/block-canvas'; import { getDuotoneFilter } from './components/duotone/utils'; import { useFlashEditableBlocks } from './components/use-flash-editable-blocks'; +import { selectBlockPatternsKey } from './store/private-keys'; /** * Private @wordpress/block-editor APIs. @@ -56,4 +57,5 @@ lock( privateApis, { useReusableBlocksRenameHint, usesContextKey, useFlashEditableBlocks, + selectBlockPatternsKey, } ); diff --git a/packages/block-editor/src/store/index.js b/packages/block-editor/src/store/index.js index 10e16a0779cd63..0bcc00cb5f6ae8 100644 --- a/packages/block-editor/src/store/index.js +++ b/packages/block-editor/src/store/index.js @@ -10,7 +10,6 @@ import reducer from './reducer'; import * as selectors from './selectors'; import * as privateActions from './private-actions'; import * as privateSelectors from './private-selectors'; -import * as resolvers from './resolvers'; import * as actions from './actions'; import { STORE_NAME } from './constants'; import { unlock } from '../lock-unlock'; @@ -23,7 +22,6 @@ import { unlock } from '../lock-unlock'; export const storeConfig = { reducer, selectors, - resolvers, actions, }; diff --git a/packages/block-editor/src/store/private-keys.js b/packages/block-editor/src/store/private-keys.js new file mode 100644 index 00000000000000..8bfa4bb68297f8 --- /dev/null +++ b/packages/block-editor/src/store/private-keys.js @@ -0,0 +1 @@ +export const selectBlockPatternsKey = Symbol( 'selectBlockPatternsKey' ); diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 4700e50f739f45..c885e43ba75208 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -22,6 +22,7 @@ import { checkAllowListRecursive, getAllPatternsDependants } from './utils'; import { INSERTER_PATTERN_TYPES } from '../components/inserter/block-patterns-tab/utils'; import { STORE_NAME } from './constants'; import { unlock } from '../lock-unlock'; +import { selectBlockPatternsKey } from './private-keys'; export { getBlockSettings } from './get-block-settings'; @@ -250,10 +251,6 @@ export const getInserterMediaCategories = createSelector( ] ); -export function getFetchedPatterns( state ) { - return state.blockPatterns; -} - /** * Returns whether there is at least one allowed pattern for inner blocks children. * This is useful for deferring the parsing of all patterns until needed. @@ -285,7 +282,7 @@ export const hasAllowedPatterns = createRegistrySelector( ( select ) => } ); }, ( state, rootClientId ) => [ - getAllPatternsDependants( state ), + getAllPatternsDependants( select )( state ), state.settings.allowedBlockTypes, state.settings.templateLock, state.blockListSettings[ rootClientId ], @@ -325,12 +322,12 @@ export const getAllPatterns = createRegistrySelector( ( select ) => return [ ...userPatterns, ...__experimentalBlockPatterns, - ...unlock( select( STORE_NAME ) ).getFetchedPatterns(), + ...( state.settings[ selectBlockPatternsKey ]?.( select ) ?? [] ), ].filter( ( x, index, arr ) => index === arr.findIndex( ( y ) => x.name === y.name ) ); - }, getAllPatternsDependants ) + }, getAllPatternsDependants( select ) ) ); /** diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 0be421b757bce1..1b535e51950164 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -2064,15 +2064,6 @@ function blockBindingsSources( state = {}, action ) { return state; } -function blockPatterns( state = [], action ) { - switch ( action.type ) { - case 'RECEIVE_BLOCK_PATTERNS': - return action.patterns; - } - - return state; -} - const combinedReducers = combineReducers( { blocks, isDragging, @@ -2105,7 +2096,6 @@ const combinedReducers = combineReducers( { openedBlockSettingsMenu, registeredInserterMediaCategories, blockBindingsSources, - blockPatterns, } ); function withAutomaticChangeReset( reducer ) { diff --git a/packages/block-editor/src/store/resolvers.js b/packages/block-editor/src/store/resolvers.js deleted file mode 100644 index 40c51d241ac676..00000000000000 --- a/packages/block-editor/src/store/resolvers.js +++ /dev/null @@ -1,17 +0,0 @@ -export const getFetchedPatterns = - () => - async ( { dispatch, select } ) => { - const { __experimentalFetchBlockPatterns } = select.getSettings(); - if ( ! __experimentalFetchBlockPatterns ) { - return []; - } - const patterns = await __experimentalFetchBlockPatterns(); - dispatch( { type: 'RECEIVE_BLOCK_PATTERNS', patterns } ); - }; - -getFetchedPatterns.shouldInvalidate = ( action ) => { - return ( - action.type === 'UPDATE_SETTINGS' && - !! action.settings.__experimentalFetchBlockPatterns - ); -}; diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 3475e2b5351c80..87d22e3e4727d7 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -2299,12 +2299,12 @@ export const __experimentalGetParsedPattern = createRegistrySelector( __unstableSkipMigrationLogs: true, } ), }; - }, getAllPatternsDependants ) + }, getAllPatternsDependants( select ) ) ); -const getAllowedPatternsDependants = ( state, rootClientId ) => { +const getAllowedPatternsDependants = ( select ) => ( state, rootClientId ) => { return [ - ...getAllPatternsDependants( state ), + ...getAllPatternsDependants( select )( state ), state.settings.allowedBlockTypes, state.settings.templateLock, state.blockListSettings[ rootClientId ], @@ -2345,7 +2345,7 @@ export const __experimentalGetAllowedPatterns = createRegistrySelector( ); return patternsAllowed; - }, getAllowedPatternsDependants ); + }, getAllowedPatternsDependants( select ) ); } ); @@ -2384,7 +2384,7 @@ export const getPatternsByBlockTypes = createRegistrySelector( ( select ) => return filteredPatterns; }, ( state, blockNames, rootClientId ) => - getAllowedPatternsDependants( state, rootClientId ) + getAllowedPatternsDependants( select )( state, rootClientId ) ) ); @@ -2458,7 +2458,7 @@ export const __experimentalGetPatternTransformItems = createRegistrySelector( ); }, ( state, blocks, rootClientId ) => - getAllowedPatternsDependants( state, rootClientId ) + getAllowedPatternsDependants( select )( state, rootClientId ) ) ); diff --git a/packages/block-editor/src/store/utils.js b/packages/block-editor/src/store/utils.js index 6cde56da1b55a7..4d9d114946c1fd 100644 --- a/packages/block-editor/src/store/utils.js +++ b/packages/block-editor/src/store/utils.js @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ +import { selectBlockPatternsKey } from './private-keys'; + export const checkAllowList = ( list, item, defaultResult = null ) => { if ( typeof list === 'boolean' ) { return list; @@ -40,12 +45,12 @@ export const checkAllowListRecursive = ( blocks, allowedBlockTypes ) => { return true; }; -export const getAllPatternsDependants = ( state ) => { +export const getAllPatternsDependants = ( select ) => ( state ) => { return [ state.settings.__experimentalBlockPatterns, state.settings.__experimentalUserPatternCategories, state.settings.__experimentalReusableBlocks, - state.settings.__experimentalFetchBlockPatterns, + state.settings[ selectBlockPatternsKey ]?.( select ), state.blockPatterns, ]; }; diff --git a/packages/core-data/src/private-selectors.ts b/packages/core-data/src/private-selectors.ts index 94aa00e1c8de45..85dc4e3be7203c 100644 --- a/packages/core-data/src/private-selectors.ts +++ b/packages/core-data/src/private-selectors.ts @@ -1,7 +1,18 @@ +/** + * External dependencies + */ +import createSelector from 'rememo'; + +/** + * WordPress dependencies + */ +import { createRegistrySelector } from '@wordpress/data'; + /** * Internal dependencies */ import type { State } from './selectors'; +import { STORE_NAME } from './name'; type EntityRecordKey = string | number; @@ -28,3 +39,19 @@ export function getNavigationFallbackId( ): EntityRecordKey | undefined { return state.navigationFallbackId; } + +export const getBlockPatternsForPostType = createRegistrySelector( + ( select: any ) => + createSelector( + ( state, postType ) => + select( STORE_NAME ) + .getBlockPatterns() + .filter( + ( { postTypes } ) => + ! postTypes || + ( Array.isArray( postTypes ) && + postTypes.includes( postType ) ) + ), + () => [ select( STORE_NAME ).getBlockPatterns() ] + ) +); diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 164f925743522c..d8973850111d1b 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -7,12 +7,12 @@ import { store as coreStore, __experimentalFetchLinkSuggestions as fetchLinkSuggestions, __experimentalFetchUrlData as fetchUrlData, - fetchBlockPatterns, } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; import { store as preferencesStore } from '@wordpress/preferences'; import { useViewportMatch } from '@wordpress/compose'; import { store as blocksStore } from '@wordpress/blocks'; +import { privateApis } from '@wordpress/block-editor'; /** * Internal dependencies @@ -20,6 +20,7 @@ import { store as blocksStore } from '@wordpress/blocks'; import inserterMediaCategories from '../media-categories'; import { mediaUpload } from '../../utils'; import { store as editorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; const EMPTY_BLOCKS_LIST = []; @@ -247,17 +248,10 @@ function useBlockEditorSettings( settings, postType, postId ) { keepCaretInsideBlock, mediaUpload: hasUploadPermissions ? mediaUpload : undefined, __experimentalBlockPatterns: blockPatterns, - __experimentalFetchBlockPatterns: async () => { - return ( await fetchBlockPatterns() ).filter( - ( { postTypes } ) => { - return ( - ! postTypes || - ( Array.isArray( postTypes ) && - postTypes.includes( postType ) ) - ); - } - ); - }, + [ unlock( privateApis ).selectBlockPatternsKey ]: ( select ) => + unlock( select( coreStore ) ).getBlockPatternsForPostType( + postType + ), __experimentalReusableBlocks: reusableBlocks, __experimentalBlockPatternCategories: blockPatternCategories, __experimentalUserPatternCategories: userPatternCategories, From 00f7e12f7afde91fb8a1329531508ca33d961526 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Thu, 15 Feb 2024 09:39:56 +0900 Subject: [PATCH 12/22] Font Libary: Add missing translation functions (#58104) Co-authored-by: t-hamano Co-authored-by: creativecoder Co-authored-by: matiasbenedetto --- .../font-library-modal/google-fonts-confirm-dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/google-fonts-confirm-dialog.js b/packages/edit-site/src/components/global-styles/font-library-modal/google-fonts-confirm-dialog.js index 67140fbe4d0d93..dde6dc87c858ac 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/google-fonts-confirm-dialog.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/google-fonts-confirm-dialog.js @@ -24,7 +24,7 @@ function GoogleFontsConfirmDialog() {
- Connect to Google Fonts + { __( 'Connect to Google Fonts' ) } { __( From 9b48e86196243ce92305bf584fe91a4250c94d16 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:41:01 +1100 Subject: [PATCH 13/22] Background image support: Fix issue with background position if keyboard entry happens before mouse interaction (#59050) Co-authored-by: andrewserong Co-authored-by: tellthemachines --- packages/block-editor/src/hooks/background.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/hooks/background.js b/packages/block-editor/src/hooks/background.js index 80b6090eca2190..1564d9ea9a81ca 100644 --- a/packages/block-editor/src/hooks/background.js +++ b/packages/block-editor/src/hooks/background.js @@ -374,11 +374,14 @@ function backgroundSizeHelpText( value ) { } export const coordsToBackgroundPosition = ( value ) => { - if ( ! value || isNaN( value.x ) || isNaN( value.y ) ) { + if ( ! value || ( isNaN( value.x ) && isNaN( value.y ) ) ) { return undefined; } - return `${ value.x * 100 }% ${ value.y * 100 }%`; + const x = isNaN( value.x ) ? 0.5 : value.x; + const y = isNaN( value.y ) ? 0.5 : value.y; + + return `${ x * 100 }% ${ y * 100 }%`; }; export const backgroundPositionToCoords = ( value ) => { From e53ba3bf6410338c79e8f478c9a33de4b333c7c6 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Feb 2024 10:46:13 +0100 Subject: [PATCH 14/22] Editor: Do not open list view by default on mobile (#59016) Co-authored-by: youknowriad Co-authored-by: ntsekouras Co-authored-by: aaronrobertshaw Co-authored-by: andrewserong Co-authored-by: t-hamano --- packages/edit-post/src/index.js | 3 +++ packages/edit-site/src/store/private-actions.js | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 08bc7c5aa7002c..527818018ad5c9 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -49,6 +49,7 @@ export function initializeEditor( settings, initialEdits ) { + const isMediumOrBigger = window.matchMedia( '(min-width: 782px)' ).matches; const target = document.getElementById( id ); const root = createRoot( target ); @@ -77,7 +78,9 @@ export function initializeEditor( // Check if the block list view should be open by default. // If `distractionFree` mode is enabled, the block list view should not be open. + // This behavior is disabled for small viewports. if ( + isMediumOrBigger && select( preferencesStore ).get( 'core', 'showListViewByDefault' ) && ! select( preferencesStore ).get( 'core', 'distractionFree' ) ) { diff --git a/packages/edit-site/src/store/private-actions.js b/packages/edit-site/src/store/private-actions.js index 930e89c6254102..fd23903a6a05e4 100644 --- a/packages/edit-site/src/store/private-actions.js +++ b/packages/edit-site/src/store/private-actions.js @@ -22,6 +22,8 @@ import { TEMPLATE_POST_TYPE } from '../utils/constants'; export const setCanvasMode = ( mode ) => ( { registry, dispatch } ) => { + const isMediumOrBigger = + window.matchMedia( '(min-width: 782px)' ).matches; registry.dispatch( blockEditorStore ).__unstableSetEditorMode( 'edit' ); dispatch( { type: 'SET_CANVAS_MODE', @@ -29,7 +31,9 @@ export const setCanvasMode = } ); // Check if the block list view should be open by default. // If `distractionFree` mode is enabled, the block list view should not be open. + // This behavior is disabled for small viewports. if ( + isMediumOrBigger && mode === 'edit' && registry .select( preferencesStore ) From 18c62dfbd7a8bda6c57b556d498b224bbc57babb Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+c4rl0sbr4v0@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:02:03 +0100 Subject: [PATCH 15/22] Refactor to use string instead of an object (#59030) Co-authored-by: c4rl0sbr4v0 Co-authored-by: gziolo --- .../interactivity-api/class-wp-interactivity-api.php | 4 ++-- .../plugins/interactive-blocks/router-navigate/render.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-6.5/interactivity-api/class-wp-interactivity-api.php b/lib/compat/wordpress-6.5/interactivity-api/class-wp-interactivity-api.php index 3bfade3aa4aa75..1fc757accf6f6b 100644 --- a/lib/compat/wordpress-6.5/interactivity-api/class-wp-interactivity-api.php +++ b/lib/compat/wordpress-6.5/interactivity-api/class-wp-interactivity-api.php @@ -800,14 +800,14 @@ public function print_router_loading_and_screen_reader_markup() { echo <<
HTML; diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php b/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php index 4abcee3de2c399..0b8e6e1012d1a4 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php @@ -18,7 +18,7 @@ ?>

From 81115e74284818f5e1082689b7e1846fee421c4d Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Thu, 15 Feb 2024 14:38:39 +0200 Subject: [PATCH 16/22] Fix DFM ui toggling bugs (#59061) * don't open post editor inspector if DFM is on, hide list view toggle in site editor if DFM is on, don't render post editor inspector if DFM is on * udpdate tests with the new call for dfm pref and add two tests for when dfm is on Co-authored-by: draganescu Co-authored-by: youknowriad Co-authored-by: scruffian --- .../editor-initialization/listener-hooks.js | 25 +++++++----- .../test/listener-hooks.js | 40 +++++++++++++++++++ .../edit-post/src/components/layout/index.js | 2 +- .../edit-site/src/components/editor/index.js | 1 + .../src/components/document-tools/index.js | 38 ++++++++++-------- 5 files changed, 80 insertions(+), 26 deletions(-) diff --git a/packages/edit-post/src/components/editor-initialization/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/listener-hooks.js index 57f5f67721ba72..73872b4d7110e4 100644 --- a/packages/edit-post/src/components/editor-initialization/listener-hooks.js +++ b/packages/edit-post/src/components/editor-initialization/listener-hooks.js @@ -5,6 +5,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { useEffect, useRef } from '@wordpress/element'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { store as editorStore } from '@wordpress/editor'; +import { store as preferencesStore } from '@wordpress/preferences'; /** * Internal dependencies @@ -22,19 +23,25 @@ import { * @param {number} postId The current post id. */ export const useBlockSelectionListener = ( postId ) => { - const { hasBlockSelection, isEditorSidebarOpened } = useSelect( - ( select ) => ( { - hasBlockSelection: - !! select( blockEditorStore ).getBlockSelectionStart(), - isEditorSidebarOpened: select( STORE_NAME ).isEditorSidebarOpened(), - } ), - [ postId ] - ); + const { hasBlockSelection, isEditorSidebarOpened, isDistractionFree } = + useSelect( + ( select ) => { + const { get } = select( preferencesStore ); + return { + hasBlockSelection: + !! select( blockEditorStore ).getBlockSelectionStart(), + isEditorSidebarOpened: + select( STORE_NAME ).isEditorSidebarOpened(), + isDistractionFree: get( 'core', 'distractionFree' ), + }; + }, + [ postId ] + ); const { openGeneralSidebar } = useDispatch( STORE_NAME ); useEffect( () => { - if ( ! isEditorSidebarOpened ) { + if ( ! isEditorSidebarOpened || isDistractionFree ) { return; } if ( hasBlockSelection ) { diff --git a/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js index c263d993982978..5087d303fafe1e 100644 --- a/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js +++ b/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js @@ -44,6 +44,12 @@ describe( 'listener hook tests', () => { isViewportMatch: jest.fn(), }, }, + 'core/preferences': { + ...storeConfig, + selectors: { + get: jest.fn(), + }, + }, [ STORE_NAME ]: { ...storeConfig, actions: { @@ -112,6 +118,7 @@ describe( 'listener hook tests', () => { 'getBlockSelectionStart', true ); + setMockReturnValue( 'core/preferences', 'get', false ); render( ); @@ -120,12 +127,14 @@ describe( 'listener hook tests', () => { ).toHaveBeenCalledWith( 'edit-post/block' ); } ); it( 'opens document sidebar if block is not selected', () => { + setMockReturnValue( STORE_NAME, 'isEditorSidebarOpened', true ); setMockReturnValue( STORE_NAME, 'isEditorSidebarOpened', true ); setMockReturnValue( 'core/block-editor', 'getBlockSelectionStart', false ); + setMockReturnValue( 'core/preferences', 'get', false ); render( ); @@ -133,6 +142,37 @@ describe( 'listener hook tests', () => { getSpyedAction( STORE_NAME, 'openGeneralSidebar' ) ).toHaveBeenCalledWith( 'edit-post/document' ); } ); + it( 'does not open block sidebar if block is selected and distraction free mode is on', () => { + setMockReturnValue( STORE_NAME, 'isEditorSidebarOpened', true ); + setMockReturnValue( + 'core/block-editor', + 'getBlockSelectionStart', + true + ); + setMockReturnValue( 'core/preferences', 'get', true ); + + render( ); + + expect( + getSpyedAction( STORE_NAME, 'openGeneralSidebar' ) + ).toHaveBeenCalledTimes( 0 ); + } ); + it( 'does not open document sidebar if block is not selected and distraction free is on', () => { + setMockReturnValue( STORE_NAME, 'isEditorSidebarOpened', true ); + setMockReturnValue( STORE_NAME, 'isEditorSidebarOpened', true ); + setMockReturnValue( + 'core/block-editor', + 'getBlockSelectionStart', + false + ); + setMockReturnValue( 'core/preferences', 'get', true ); + + render( ); + + expect( + getSpyedAction( STORE_NAME, 'openGeneralSidebar' ) + ).toHaveBeenCalledTimes( 0 ); + } ); } ); describe( 'useUpdatePostLinkListener', () => { diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 9dd7314357e043..eb2b696fa39706 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -385,7 +385,7 @@ function Layout( { initialPost } ) { - + { ! isDistractionFree && } ); } diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 9c95755db28031..6563374858a37f 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -263,6 +263,7 @@ export default function Editor( { isLoading } ) { ( shouldShowListView && ) ) } sidebar={ + ! isDistractionFree && isEditMode && isRightSidebarOpen && ( <> diff --git a/packages/editor/src/components/document-tools/index.js b/packages/editor/src/components/document-tools/index.js index 05907654fa9b84..26adcb1bb725a7 100644 --- a/packages/editor/src/components/document-tools/index.js +++ b/packages/editor/src/components/document-tools/index.js @@ -46,6 +46,7 @@ function DocumentTools( { const { setIsInserterOpened, setIsListViewOpened } = useDispatch( editorStore ); const { + isDistractionFree, isInserterOpened, isListViewOpen, listViewShortcut, @@ -69,6 +70,7 @@ function DocumentTools( { listViewToggleRef: getListViewToggleRef(), hasFixedToolbar: getSettings().hasFixedToolbar, showIconLabels: get( 'core', 'showIconLabels' ), + isDistractionFree: get( 'core', 'distractionFree' ), }; }, [] ); @@ -158,22 +160,26 @@ function DocumentTools( { variant={ showIconLabels ? 'tertiary' : undefined } size="compact" /> - + { ! isDistractionFree && ( + + ) } ) } { children } From 295804391b476b808812fed149f65726837db210 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:33:56 +0100 Subject: [PATCH 17/22] Rich text: fix link paste for internal paste (#59063) Co-authored-by: ellatrix Co-authored-by: mcsf Co-authored-by: youknowriad Co-authored-by: annezazu --- .../components/rich-text/use-paste-handler.js | 51 ++++++++++--------- .../editor/various/copy-cut-paste.spec.js | 24 +++++++++ 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/use-paste-handler.js b/packages/block-editor/src/components/rich-text/use-paste-handler.js index 1302e2d0dce469..fc75caa43615f5 100644 --- a/packages/block-editor/src/components/rich-text/use-paste-handler.js +++ b/packages/block-editor/src/components/rich-text/use-paste-handler.js @@ -58,13 +58,35 @@ export function usePasteHandler( props ) { const isInternal = event.clipboardData.getData( 'rich-text' ) === 'true'; + function pasteInline( content ) { + const transformed = formatTypes.reduce( + ( accumulator, { __unstablePasteRule } ) => { + // Only allow one transform. + if ( __unstablePasteRule && accumulator === value ) { + accumulator = __unstablePasteRule( value, { + html, + plainText, + } ); + } + + return accumulator; + }, + value + ); + if ( transformed !== value ) { + onChange( transformed ); + } else { + const valueToInsert = create( { html: content } ); + addActiveFormats( valueToInsert, value.activeFormats ); + onChange( insert( value, valueToInsert ) ); + } + } + // If the data comes from a rich text instance, we can directly use it // without filtering the data. The filters are only meant for externally // pasted content and remove inline styles. if ( isInternal ) { - const pastedValue = create( { html } ); - addActiveFormats( pastedValue, value.activeFormats ); - onChange( insert( value, pastedValue ) ); + pasteInline( html ); return; } @@ -135,28 +157,7 @@ export function usePasteHandler( props ) { } ); if ( typeof content === 'string' ) { - const transformed = formatTypes.reduce( - ( accumlator, { __unstablePasteRule } ) => { - // Only allow one transform. - if ( __unstablePasteRule && accumlator === value ) { - accumlator = __unstablePasteRule( value, { - html, - plainText, - } ); - } - - return accumlator; - }, - value - ); - - if ( transformed !== value ) { - onChange( transformed ); - } else { - const valueToInsert = create( { html: content } ); - addActiveFormats( valueToInsert, value.activeFormats ); - onChange( insert( value, valueToInsert ) ); - } + pasteInline( content ); } else if ( content.length > 0 ) { if ( onReplace && isEmpty( value ) ) { onReplace( content, content.length - 1, -1 ); diff --git a/test/e2e/specs/editor/various/copy-cut-paste.spec.js b/test/e2e/specs/editor/various/copy-cut-paste.spec.js index 04113e013930b0..ec0cbab9ca809b 100644 --- a/test/e2e/specs/editor/various/copy-cut-paste.spec.js +++ b/test/e2e/specs/editor/various/copy-cut-paste.spec.js @@ -518,6 +518,30 @@ test.describe( 'Copy/cut/paste', () => { ] ); } ); + test( 'should link selection on internal paste', async ( { + pageUtils, + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { content: 'https://w.org' }, + } ); + await pageUtils.pressKeys( 'primary+a' ); + await pageUtils.pressKeys( 'primary+x' ); + await page.keyboard.type( 'a' ); + await pageUtils.pressKeys( 'shift+ArrowLeft' ); + await pageUtils.pressKeys( 'primary+v' ); + expect( await editor.getBlocks() ).toMatchObject( [ + { + name: 'core/paragraph', + attributes: { + content: 'a', + }, + }, + ] ); + } ); + test( 'should auto-link', async ( { pageUtils, editor } ) => { await editor.insertBlock( { name: 'core/paragraph', From 974f54568a85e45247556e16e099b266fe63ced9 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 15 Feb 2024 18:19:21 +0400 Subject: [PATCH 18/22] Image: Mark connected controls as 'readyonly' (#59059) * Image: Mark connected controls as 'readyonly' * Update e2e tests Co-authored-by: Mamaduka Co-authored-by: michalczaplinski Co-authored-by: afercia --- test/e2e/specs/editor/various/block-bindings.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/specs/editor/various/block-bindings.spec.js b/test/e2e/specs/editor/various/block-bindings.spec.js index 6d0aa11563730d..fc315e522b81ac 100644 --- a/test/e2e/specs/editor/various/block-bindings.spec.js +++ b/test/e2e/specs/editor/various/block-bindings.spec.js @@ -939,7 +939,7 @@ test.describe( 'Block bindings', () => { page .getByRole( 'tabpanel', { name: 'Settings' } ) .getByLabel( 'Alternative text' ) - ).toBeDisabled(); + ).toHaveAttribute( 'readonly' ); const altValue = await page .getByRole( 'tabpanel', { name: 'Settings' } ) .getByLabel( 'Alternative text' ) @@ -1081,7 +1081,7 @@ test.describe( 'Block bindings', () => { page .getByRole( 'tabpanel', { name: 'Settings' } ) .getByLabel( 'Title attribute' ) - ).toBeDisabled(); + ).toHaveAttribute( 'readonly' ); const titleValue = await page .getByRole( 'tabpanel', { name: 'Settings' } ) .getByLabel( 'Title attribute' ) From ea8ea99366ef11393c0c80cccf942fa1a95c58a7 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Fri, 16 Feb 2024 13:53:08 +0900 Subject: [PATCH 19/22] Global Styles: fix console error in block preview (#59112) Co-authored-by: t-hamano Co-authored-by: Mamaduka Co-authored-by: annezazu Co-authored-by: carolinan --- .../src/components/global-styles/block-preview-panel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/block-preview-panel.js b/packages/edit-site/src/components/global-styles/block-preview-panel.js index 24bd2e666eab3e..787d8c6d47e500 100644 --- a/packages/edit-site/src/components/global-styles/block-preview-panel.js +++ b/packages/edit-site/src/components/global-styles/block-preview-panel.js @@ -28,7 +28,7 @@ const BlockPreviewPanel = ( { name, variation = '' } ) => { }, [ name, blockExample, variation ] ); const viewportWidth = blockExample?.viewportWidth ?? null; - const previewHeight = '150px'; + const previewHeight = 150; if ( ! blockExample ) { return null; @@ -48,7 +48,7 @@ const BlockPreviewPanel = ( { name, variation = '' } ) => { { css: ` body{ - min-height:${ previewHeight }; + min-height:${ previewHeight }px; display:flex;align-items:center;justify-content:center; } `, From 1236a04ada288343aafaab848357a8c3a84c06b5 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Feb 2024 12:13:06 +0100 Subject: [PATCH 20/22] Site Editor: Fix navigation on mobile web (#59014) Co-authored-by: youknowriad Co-authored-by: t-hamano Co-authored-by: ntsekouras Co-authored-by: jameskoster Co-authored-by: colorful-tones Co-authored-by: annezazu --- .../edit-site/src/components/layout/index.js | 77 +++++++++++-------- .../edit-site/src/components/layout/router.js | 33 +++++++- .../src/components/layout/style.scss | 7 ++ .../src/components/page-patterns/style.scss | 6 ++ .../sidebar-navigation-screen-pages/index.js | 16 ++-- .../edit-site/src/components/sidebar/index.js | 10 ++- 6 files changed, 108 insertions(+), 41 deletions(-) diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 163d4fe6e938ff..65dfb71949a7f5 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -251,47 +251,64 @@ export default function Layout() { The NavigableRegion must always be rendered and not use `inert` otherwise `useNavigateRegions` will fail. */ } - - - { canvasMode === 'view' && ( - - - - ) } - - + { ( ! isMobileViewport || + ( isMobileViewport && ! areas.mobile ) ) && ( + + + { canvasMode === 'view' && ( + + + + ) } + + + ) } - { areas.content && canvasMode !== 'edit' && ( + { isMobileViewport && areas.mobile && (
- { areas.content } + { areas.mobile }
) } - { areas.preview && ( + { ! isMobileViewport && + areas.content && + canvasMode !== 'edit' && ( +
+ { areas.content } +
+ ) } + + { ! isMobileViewport && areas.preview && (
{ canvasResizer } { !! canvasSize.width && ( diff --git a/packages/edit-site/src/components/layout/router.js b/packages/edit-site/src/components/layout/router.js index aeb124bf76fe5f..02f7e7b25594ae 100644 --- a/packages/edit-site/src/components/layout/router.js +++ b/packages/edit-site/src/components/layout/router.js @@ -23,13 +23,21 @@ const { useLocation } = unlock( routerPrivateApis ); export default function useLayoutAreas() { const isSiteEditorLoading = useIsSiteEditorLoading(); const { params } = useLocation(); - const { postType, postId, path, layout, isCustom } = params ?? {}; + const { postType, postId, path, layout, isCustom, canvas } = params ?? {}; + + // Note: Since "sidebar" is not yet supported here, + // returning undefined from "mobile" means show the sidebar. + // Regular page if ( path === '/page' ) { return { areas: { content: undefined, preview: , + mobile: + canvas === 'edit' ? ( + + ) : undefined, }, widths: { content: undefined, @@ -63,6 +71,10 @@ export default function useLayoutAreas() { return { areas: { preview: , + mobile: + canvas === 'edit' ? ( + + ) : undefined, }, }; } @@ -79,6 +91,11 @@ export default function useLayoutAreas() { preview: isListLayout && ( ), + mobile: ( + + ), }, widths: { content: isListLayout ? 380 : undefined, @@ -98,6 +115,11 @@ export default function useLayoutAreas() { preview: isListLayout && ( ), + mobile: ( + + ), }, widths: { content: isListLayout ? 380 : undefined, @@ -110,12 +132,19 @@ export default function useLayoutAreas() { return { areas: { content: , + mobile: , }, }; } // Fallback shows the home page preview return { - areas: { preview: }, + areas: { + preview: , + mobile: + canvas === 'edit' ? ( + + ) : undefined, + }, }; } diff --git a/packages/edit-site/src/components/layout/style.scss b/packages/edit-site/src/components/layout/style.scss index 9be0b001ed9e25..ad33768989a81b 100644 --- a/packages/edit-site/src/components/layout/style.scss +++ b/packages/edit-site/src/components/layout/style.scss @@ -83,6 +83,12 @@ flex-direction: column; } +.edit-site-layout__mobile { + position: relative; + width: 100%; + z-index: z-index(".edit-site-layout__canvas-container"); +} + .edit-site-layout__canvas-container { position: relative; flex-grow: 1; @@ -147,6 +153,7 @@ } // This shouldn't be necessary (we should have a way to say that a skeletton is relative +.edit-site-layout__mobile .interface-interface-skeleton, .edit-site-layout__canvas .interface-interface-skeleton, .edit-site-template-pages-preview .interface-interface-skeleton { position: relative !important; diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index 0be55756d1609d..dd32818edd614b 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -225,6 +225,12 @@ * TODO: when this becomes stable, consolidate styles with the above. */ .edit-site-page-patterns-dataviews { + margin-top: 60px; + + @include break-medium { + margin-top: 0; + } + .page-patterns-preview-field { display: flex; flex-direction: column; 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 82fc6d0fa2412b..7641ac1fe97a0d 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 @@ -14,6 +14,7 @@ import { decodeEntities } from '@wordpress/html-entities'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { layout, page, home, verse, plus } from '@wordpress/icons'; import { useSelect } from '@wordpress/data'; +import { useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies @@ -42,6 +43,7 @@ const PageItem = ( { postType = 'page', postId, ...props } ) => { }; export default function SidebarNavigationScreenPages() { + const isMobileViewport = useViewportMatch( 'medium', '<' ); const { records: pages, isResolving: isLoadingPages } = useEntityRecords( 'postType', 'page', @@ -220,12 +222,14 @@ export default function SidebarNavigationScreenPages() { ) ) } - - { __( 'Manage all pages' ) } - + { ! isMobileViewport && ( + + { __( 'Manage all pages' ) } + + ) } } /> diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index 84a89957b475d5..56107edc63c863 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -13,6 +13,7 @@ import { } from '@wordpress/components'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { __ } from '@wordpress/i18n'; +import { useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies @@ -52,6 +53,7 @@ function SidebarScreenWrapper( { className, ...props } ) { function SidebarScreens() { useSyncPathWithURL(); + const isMobileViewport = useViewportMatch( 'medium', '<' ); return ( <> @@ -82,9 +84,11 @@ function SidebarScreens() { - - - + { ! isMobileViewport && ( + + + + ) } From 21815917bbdd809eebe058a593f682fc0327c154 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Mon, 19 Feb 2024 11:15:58 +0200 Subject: [PATCH 21/22] Revert footer in pages list with DataViews (#59151) Co-authored-by: ntsekouras Co-authored-by: youknowriad --- packages/edit-site/src/components/sidebar/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index 56107edc63c863..30467c7a51047e 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -14,6 +14,7 @@ import { import { privateApis as routerPrivateApis } from '@wordpress/router'; import { __ } from '@wordpress/i18n'; import { useViewportMatch } from '@wordpress/compose'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies From dfe3d0dca65a1d25592f80248b346c4a14846d9a Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 20 Feb 2024 15:42:24 +0100 Subject: [PATCH 22/22] Fix build --- packages/base-styles/_z-index.scss | 1 + packages/edit-site/src/components/sidebar/index.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index ff21d1d8df8f35..7152794975f420 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -190,6 +190,7 @@ $z-layers: ( ".edit-site-page-header": 2, ".edit-site-page-content": 1, ".edit-site-patterns__header": 2, + ".edit-site-patterns__grid-pagination": 2, ".edit-site-patterns__dataviews-list-pagination": 2, ".edit-site-templates__dataviews-list-pagination": 2, ".edit-site-layout__canvas-container": 2, diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index 30467c7a51047e..56107edc63c863 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -14,7 +14,6 @@ import { import { privateApis as routerPrivateApis } from '@wordpress/router'; import { __ } from '@wordpress/i18n'; import { useViewportMatch } from '@wordpress/compose'; -import { __ } from '@wordpress/i18n'; /** * Internal dependencies