From 87d761eb48820936f71568eb81d7130be4dfc25a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <4710635+ellatrix@users.noreply.github.com> Date: Fri, 20 Aug 2021 21:54:23 +0300 Subject: [PATCH 001/214] Block editor: iframe: render head and body with single portal (#34208) --- .../src/components/block-preview/auto.js | 6 +- .../src/components/iframe/index.js | 122 ++++++++---------- .../components/writing-flow/use-tab-nav.js | 12 +- 3 files changed, 63 insertions(+), 77 deletions(-) diff --git a/packages/block-editor/src/components/block-preview/auto.js b/packages/block-editor/src/components/block-preview/auto.js index 89030f65439c06..c0072357257d84 100644 --- a/packages/block-editor/src/components/block-preview/auto.js +++ b/packages/block-editor/src/components/block-preview/auto.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { Disabled } from '@wordpress/components'; -import { useResizeObserver, pure } from '@wordpress/compose'; +import { useResizeObserver, pure, useRefEffect } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; /** @@ -46,7 +46,7 @@ function AutoBlockPreview( { viewportWidth, __experimentalPadding } ) { > { tabIndex >= 0 && after } diff --git a/packages/block-editor/src/components/writing-flow/use-tab-nav.js b/packages/block-editor/src/components/writing-flow/use-tab-nav.js index 96a6d522b46294..3b3b3cf607d45b 100644 --- a/packages/block-editor/src/components/writing-flow/use-tab-nav.js +++ b/packages/block-editor/src/components/writing-flow/use-tab-nav.js @@ -179,17 +179,13 @@ export default function useTabNav() { } } - node.ownerDocument.defaultView.addEventListener( - 'keydown', - preventScrollOnTab - ); + const { ownerDocument } = node; + const { defaultView } = ownerDocument; + defaultView.addEventListener( 'keydown', preventScrollOnTab ); node.addEventListener( 'keydown', onKeyDown ); node.addEventListener( 'focusout', onFocusOut ); return () => { - node.ownerDocument.defaultView.removeEventListener( - 'keydown', - preventScrollOnTab - ); + defaultView.removeEventListener( 'keydown', preventScrollOnTab ); node.removeEventListener( 'keydown', onKeyDown ); node.removeEventListener( 'focusout', onFocusOut ); }; From 0460fda94fc08f6ff5d893ff7274cb5e9c137c55 Mon Sep 17 00:00:00 2001 From: Patrick Boehner Date: Sat, 21 Aug 2021 19:25:29 -0700 Subject: [PATCH 002/214] Update create-block-theme.md (#34152) Spelling correction for "typography" in fontSize settings code examples. --- docs/how-to-guides/themes/create-block-theme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/how-to-guides/themes/create-block-theme.md b/docs/how-to-guides/themes/create-block-theme.md index 72fea1cb8ec975..45df340c1e19df 100644 --- a/docs/how-to-guides/themes/create-block-theme.md +++ b/docs/how-to-guides/themes/create-block-theme.md @@ -628,7 +628,7 @@ To add custom font sizes, create a new section called `typography` under `settin `fontSizes` is the equivalent of `add_theme_support( 'editor-font-sizes' )`. ```json -"typograhy": { +"typography": { "fontSizes": [ ] } @@ -641,7 +641,7 @@ The keys used by `fontSizes` are: - `name` The visible name in the editor. ```json -"typograhy": { +"typography": { "fontSizes": [ { "slug": "normal", From 5f0953667ce10d8085d14243fd817e7768ccd692 Mon Sep 17 00:00:00 2001 From: Tanner Stokes Date: Sat, 21 Aug 2021 22:26:25 -0400 Subject: [PATCH 003/214] Correct minor typos. (#34185) --- docs/getting-started/tutorials/create-block/wp-plugin.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/getting-started/tutorials/create-block/wp-plugin.md b/docs/getting-started/tutorials/create-block/wp-plugin.md index b3d4a4a636e8c0..ff1d1db7f455cd 100644 --- a/docs/getting-started/tutorials/create-block/wp-plugin.md +++ b/docs/getting-started/tutorials/create-block/wp-plugin.md @@ -59,7 +59,7 @@ This will start your local WordPress site and use the current directory as your ### Confirm Plugin Installed -The generated plugin should now be listed on the Plugins admin page in your WordPress install. Switch WorPress to the plugins page and activate. +The generated plugin should now be listed on the Plugins admin page in your WordPress install. Switch WordPress to the plugins page and activate. For more on creating a WordPress plugin see [Plugin Basics](https://developer.wordpress.org/plugins/plugin-basics/), and [Plugin Header requirements](https://developer.wordpress.org/plugins/plugin-basics/header-requirements/) for explanation and additional fields you can include in your plugin header. @@ -67,7 +67,7 @@ For more on creating a WordPress plugin see [Plugin Basics](https://developer.wo The `package.json` file defines the JavaScript properties for your project. This is a standard file used by NPM for defining properties and scripts it can run, the file and process is not specific to WordPress. -A `package.json` file was created with the create script, this defines the dependecies and scripts needed. You can install dependencies. The only initial dependency is the `@wordpress/scripts` package that bundles the tools and configurations needed to build blocks. +A `package.json` file was created with the create script, this defines the dependencies and scripts needed. You can install dependencies. The only initial dependency is the `@wordpress/scripts` package that bundles the tools and configurations needed to build blocks. In `package.json`, there is a `scripts` property that defines what command to run when using `npm run (cmd)`. In our generated `package.json` file, the two main scripts point to the commands in the `wp-scripts` package: From 3e16bd17a56258208d99dff16fdb476691d0d3f3 Mon Sep 17 00:00:00 2001 From: ssergei Date: Sat, 21 Aug 2021 18:41:16 -0800 Subject: [PATCH 004/214] Update legacy-widget-block.md (#34103) fixed misspelling --- docs/how-to-guides/widgets/legacy-widget-block.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to-guides/widgets/legacy-widget-block.md b/docs/how-to-guides/widgets/legacy-widget-block.md index 7e629d482688cb..e349829acb25c8 100644 --- a/docs/how-to-guides/widgets/legacy-widget-block.md +++ b/docs/how-to-guides/widgets/legacy-widget-block.md @@ -33,7 +33,7 @@ Note that all of the widget's event handlers are added in the `widget-added` cal The Legacy Widget block will display a preview of the widget when the Legacy Widget block is not selected. -A "No preview available." message is automatically shown by the Legacy Widget block when the widget's `widget()` function does not render anytihng or only renders empty HTML elements. +A "No preview available." message is automatically shown by the Legacy Widget block when the widget's `widget()` function does not render anything or only renders empty HTML elements. Widgets may take advantage of this by returning early from `widget()` when a preview should not be displayed. From c322dffdab10dd8f0f6943bb76dfa74c97376301 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 23 Aug 2021 11:09:44 +1000 Subject: [PATCH 005/214] Adjust CODEOWNERS for @noisysocks --- .github/CODEOWNERS | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 934b853ba6c616..85d23fb60f268c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -40,9 +40,11 @@ # Widgets /packages/edit-widgets @draganescu @talldan @noisysocks @tellthemachines @adamziel @kevin940726 +/packages/customize-widgets @noisysocks +/packages/widgets @noisysocks # Navigation -/packages/edit-navigation @draganescu @talldan @noisysocks @tellthemachines @adamziel @kevin940726 @getdave +/packages/edit-navigation @draganescu @talldan @tellthemachines @adamziel @kevin940726 @getdave # Full Site Editing /packages/edit-site From cfc5d8b894d7a45a88f110549d52c99a9cac2450 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Mon, 23 Aug 2021 14:13:47 +0800 Subject: [PATCH 006/214] Update navigation screen topbar (#34166) * Restore normal interface layout to the nav editor * Update header layout and styles * Make the heading match the widgets editor * Hide subtitle on mobile * Adjust spacing between topbar and editor area * Use boolean prop Co-authored-by: Kai Hao Co-authored-by: Kai Hao --- .../src/components/editor/style.scss | 7 ++++- .../src/components/header/index.js | 14 ++++----- .../src/components/header/style.scss | 19 ++++++------ .../src/components/layout/index.js | 16 ++-------- .../src/components/layout/style.scss | 31 ------------------- .../src/components/sidebar/index.js | 7 +++-- 6 files changed, 28 insertions(+), 66 deletions(-) diff --git a/packages/edit-navigation/src/components/editor/style.scss b/packages/edit-navigation/src/components/editor/style.scss index bce96deb1515a5..eaa1ae7e3221e4 100644 --- a/packages/edit-navigation/src/components/editor/style.scss +++ b/packages/edit-navigation/src/components/editor/style.scss @@ -3,7 +3,12 @@ border: $border-width solid $gray-900; border-radius: $radius-block-ui; max-width: $navigation-editor-width; - margin: auto; + margin: $grid-unit-40 auto 0 auto; + + @include break-medium() { + // Provide space for the floating block toolbar. + margin-top: $grid-unit-50 * 2; + } .editor-styles-wrapper { padding: 0; diff --git a/packages/edit-navigation/src/components/header/index.js b/packages/edit-navigation/src/components/header/index.js index c923caf5b17676..49cf4cb972f536 100644 --- a/packages/edit-navigation/src/components/header/index.js +++ b/packages/edit-navigation/src/components/header/index.js @@ -38,14 +38,12 @@ export default function Header( { return (
-
-

- { __( 'Navigation' ) } -

-

- { isMenuSelected && actionHeaderText } -

-
+

+ { __( 'Navigation' ) } +

+

+ { isMenuSelected && actionHeaderText } +

{ isMenuSelected && (
} sidebar={ - ( hasPermanentSidebar || - hasSidebarEnabled ) && ( + hasSidebarEnabled && ( ) } @@ -190,7 +179,6 @@ export default function Layout( { blockEditorSettings } ) { onSelectMenu={ selectMenu } onDeleteMenu={ deleteMenu } isMenuBeingDeleted={ isMenuBeingDeleted } - hasPermanentSidebar={ hasPermanentSidebar } /> ) } diff --git a/packages/edit-navigation/src/components/layout/style.scss b/packages/edit-navigation/src/components/layout/style.scss index 9689f2ecc13850..0d84561fc19a8c 100644 --- a/packages/edit-navigation/src/components/layout/style.scss +++ b/packages/edit-navigation/src/components/layout/style.scss @@ -34,45 +34,14 @@ // Reference element for the block popover position. position: relative; - // The 10px match that of similar settings pages. - padding: $grid-unit-15 10px 10px 10px; - - @include break-medium() { - // Provide space for the floating block toolbar. - padding-top: $navigation-editor-spacing-top; - } - // Ensure the entire layout is full-height, the background // of the editing canvas needs to be full-height for block // deselection to work. flex-grow: 1; } - .interface-interface-skeleton__header { - border-bottom-color: transparent; - } - - // Force the sidebar to the right side of the screen on larger - // breakpoints. - &.has-permanent-sidebar .interface-interface-skeleton__sidebar { - position: fixed !important; - top: $grid-unit-40; - right: 0; - bottom: 0; - left: auto; - - // Hide the toggle as the sidebar should be permanently open. - .edit-navigation-sidebar__panel-tabs > button { - display: none; - } - } - .edit-navigation-header { background: $white; - - @include break-medium() { - background: transparent; - } } } diff --git a/packages/edit-navigation/src/components/sidebar/index.js b/packages/edit-navigation/src/components/sidebar/index.js index 4123ef911e146a..c6301230b1e587 100644 --- a/packages/edit-navigation/src/components/sidebar/index.js +++ b/packages/edit-navigation/src/components/sidebar/index.js @@ -13,6 +13,7 @@ import { ComplementaryArea, store as interfaceStore, } from '@wordpress/interface'; +import { useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies @@ -29,8 +30,8 @@ export default function Sidebar( { isMenuBeingDeleted, onDeleteMenu, onSelectMenu, - hasPermanentSidebar, } ) { + const isLargeViewport = useViewportMatch( 'medium' ); const { sidebar, hasBlockSelection, hasSidebarEnabled } = useSelect( ( select ) => { const _sidebar = select( @@ -79,10 +80,10 @@ export default function Sidebar( { scope={ SIDEBAR_SCOPE } identifier={ sidebarName } icon={ cog } - isActiveByDefault={ hasPermanentSidebar } + isActiveByDefault={ isLargeViewport } header={ } headerClassName="edit-navigation-sidebar__panel-tabs" - isPinnable={ ! hasPermanentSidebar } + isPinnable > { sidebarName === SIDEBAR_MENU && ( <> From 9385d9c0c4a7a373e2c757d06bd71f8d989d6167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Mon, 23 Aug 2021 09:39:02 +0200 Subject: [PATCH 007/214] Block Editor: Try groups for InspectorControls (#34069) * Block Editor: Try groups for InspectorControls * Update packages/block-editor/src/components/inspector-controls/fill.js * Remove the `block` group in InspectorControls * Make the group in InspectorControls experimental * Update React Native implementation to match web --- packages/block-editor/README.md | 4 +- .../src/components/block-inspector/index.js | 18 +- packages/block-editor/src/components/index.js | 6 +- .../src/components/index.native.js | 6 +- .../inspector-advanced-controls/README.md | 72 ------ .../inspector-advanced-controls/index.js | 32 --- .../components/inspector-controls/README.md | 241 ++++-------------- .../src/components/inspector-controls/fill.js | 32 +++ .../{index.native.js => fill.native.js} | 28 +- .../components/inspector-controls/groups.js | 14 + .../components/inspector-controls/index.js | 34 ++- .../src/components/inspector-controls/slot.js | 30 +++ .../inspector-controls/slot.native.js | 22 ++ packages/block-editor/src/hooks/anchor.js | 6 +- .../src/hooks/custom-class-name.js | 6 +- packages/block-library/src/button/edit.js | 5 +- packages/block-library/src/group/edit.js | 6 +- packages/block-library/src/image/image.js | 5 +- packages/block-library/src/post-terms/edit.js | 6 +- .../block-library/src/query/edit/index.js | 6 +- .../template-part/edit/advanced-controls.js | 6 +- 21 files changed, 211 insertions(+), 374 deletions(-) delete mode 100644 packages/block-editor/src/components/inspector-advanced-controls/README.md delete mode 100644 packages/block-editor/src/components/inspector-advanced-controls/index.js create mode 100644 packages/block-editor/src/components/inspector-controls/fill.js rename packages/block-editor/src/components/inspector-controls/{index.native.js => fill.native.js} (57%) create mode 100644 packages/block-editor/src/components/inspector-controls/groups.js create mode 100644 packages/block-editor/src/components/inspector-controls/slot.js create mode 100644 packages/block-editor/src/components/inspector-controls/slot.native.js diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 03ef4ee43b0381..b742a81e3dc8a0 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -438,9 +438,7 @@ Undocumented declaration. ### InspectorAdvancedControls -_Related_ - -- +Undocumented declaration. ### InspectorControls diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 2a8f7ffeb40dbc..0ef6a790d16884 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -19,8 +19,10 @@ import { useSelect } from '@wordpress/data'; */ import SkipToSelectedBlock from '../skip-to-selected-block'; import BlockCard from '../block-card'; -import InspectorControls from '../inspector-controls'; -import InspectorAdvancedControls from '../inspector-advanced-controls'; +import { + default as InspectorControls, + InspectorAdvancedControls, +} from '../inspector-controls'; import BlockStyles from '../block-styles'; import MultiSelectionInspector from '../multi-selection-inspector'; import DefaultStylePicker from '../default-style-picker'; @@ -128,18 +130,15 @@ const BlockInspectorSingleBlock = ( { ) }
- +
); }; -const AdvancedControls = ( { slotName, bubblesVirtually } ) => { - const slot = useSlot( slotName ); +const AdvancedControls = ( { bubblesVirtually } ) => { + const slot = useSlot( InspectorAdvancedControls.slotName ); const hasFills = Boolean( slot.fills && slot.fills.length ); if ( ! hasFills ) { @@ -152,7 +151,8 @@ const AdvancedControls = ( { slotName, bubblesVirtually } ) => { title={ __( 'Advanced' ) } initialOpen={ false } > - diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 8cf025bdcc10a5..99c1042b991730 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -52,8 +52,10 @@ export { default as InnerBlocks, useInnerBlocksProps as __experimentalUseInnerBlocksProps, } from './inner-blocks'; -export { default as InspectorAdvancedControls } from './inspector-advanced-controls'; -export { default as InspectorControls } from './inspector-controls'; +export { + default as InspectorControls, + InspectorAdvancedControls, +} from './inspector-controls'; export { JustifyToolbar, JustifyContentControl, diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index 89fa80d8b44c83..995042b7d65cdf 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -22,8 +22,10 @@ export { default as InnerBlocks, useInnerBlocksProps as __experimentalUseInnerBlocksProps, } from './inner-blocks'; -export { default as InspectorAdvancedControls } from './inspector-advanced-controls'; -export { default as InspectorControls } from './inspector-controls'; +export { + default as InspectorControls, + InspectorAdvancedControls, +} from './inspector-controls'; export { JustifyToolbar, JustifyContentControl, diff --git a/packages/block-editor/src/components/inspector-advanced-controls/README.md b/packages/block-editor/src/components/inspector-advanced-controls/README.md deleted file mode 100644 index b14b0cb6a73e24..00000000000000 --- a/packages/block-editor/src/components/inspector-advanced-controls/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# InspectorAdvancedControls - -inspector-advanced-controls - -Inspector Advanced Controls appear under the _Advanced_ panel of a block's [InspectorControls](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inspector-controls/README.md) -- that is, they appear as a specific set of controls within a block's settings panels. As the name suggests, `InspectorAdvancedControls` is meant for controls that most users aren't meant to interact with most of the time, such as adding an HTML anchor or custom CSS classes to a block. - -## Usage - -{% codetabs %} -{% ESNext %} - -```js -const { - TextControl, -} = wp.components; -const { - InspectorControls, - InspectorAdvancedControls, -} = wp.editor; - -function MyBlockEdit( { attributes, setAttributes } ) { - return ( - <> -
- { /* Block markup goes here */ } -
- { /* Regular control goes here */ - - - { - setAttributes( { - anchor: nextValue, - } ); - } } - /> - - - ); -} -``` - -{% ES5 %} - -```js -var el = wp.element.createElement, - Fragment = wp.element.Fragment, - InspectorControls = wp.editor.InspectorControls, - InspectorAdvancedControlsControls = wp.editor.InspectorAdvancedControls, - TextControl = wp.components.TextControl, - -function MyBlockEdit( props ) { - return el( Fragment, null, - el( 'div', null, /* Block markup goes here */ null ), - el( InspectorControls, null, /* Regular control goes here */ null ), - el( InspectorAdvancedControls, null, - el( TextControl, { - label: 'HTML anchor', - value: props.attributes.anchor, - onChange: function( nextValue ) { - props.setAttributes( { anchor: nextValue } ); - } - } ) - ) - ); -} -``` - -{% end %} diff --git a/packages/block-editor/src/components/inspector-advanced-controls/index.js b/packages/block-editor/src/components/inspector-advanced-controls/index.js deleted file mode 100644 index 4ed9b6341142d8..00000000000000 --- a/packages/block-editor/src/components/inspector-advanced-controls/index.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * WordPress dependencies - */ -import { - createSlotFill, - __experimentalStyleProvider as StyleProvider, -} from '@wordpress/components'; - -/** - * Internal dependencies - */ -import { useBlockEditContext } from '../block-edit/context'; - -const name = 'InspectorAdvancedControls'; -const { Fill, Slot } = createSlotFill( name ); - -function InspectorAdvancedControls( { children } ) { - const { isSelected } = useBlockEditContext(); - return isSelected ? ( - - { children } - - ) : null; -} - -InspectorAdvancedControls.slotName = name; -InspectorAdvancedControls.Slot = Slot; - -/** - * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inspector-advanced-controls/README.md - */ -export default InspectorAdvancedControls; diff --git a/packages/block-editor/src/components/inspector-controls/README.md b/packages/block-editor/src/components/inspector-controls/README.md index 8310ae3a8db7e2..2799431917b75e 100644 --- a/packages/block-editor/src/components/inspector-controls/README.md +++ b/packages/block-editor/src/components/inspector-controls/README.md @@ -6,201 +6,6 @@ Inspector Controls appear in the post settings sidebar when a block is being edi ## Usage -{% codetabs %} -{% ES5 %} - -```js -var el = wp.element.createElement, - Fragment = wp.element.Fragment, - registerBlockType = wp.blocks.registerBlockType, - RichText = wp.editor.RichText, - InspectorControls = wp.blockEditor.InspectorControls, - useBlockProps = wp.blockEditor.useBlockProps, - CheckboxControl = wp.components.CheckboxControl, - RadioControl = wp.components.RadioControl, - TextControl = wp.components.TextControl, - ToggleControl = wp.components.ToggleControl, - SelectControl = wp.components.SelectControl, - PanelBody = wp.components.PanelBody; - -registerBlockType( 'my-plugin/inspector-controls-example', { - apiVersion: 2, - - title: 'Inspector controls example', - - icon: 'universal-access-alt', - - category: 'design', - - attributes: { - content: { - type: 'string', - source: 'html', - selector: 'p', - }, - checkboxField: { - type: 'boolean', - default: true, - }, - radioField: { - type: 'string', - default: 'yes', - }, - textField: { - type: 'string', - }, - toggleField: { - type: 'boolean', - }, - selectField: { - type: 'string', - }, - }, - - edit: function ( props ) { - var blockProps = useBlockProps(); - - var content = props.attributes.content, - checkboxField = props.attributes.checkboxField, - radioField = props.attributes.radioField, - textField = props.attributes.textField, - toggleField = props.attributes.toggleField, - selectField = props.attributes.selectField; - - function onChangeContent( newContent ) { - props.setAttributes( { content: newContent } ); - } - - function onChangeCheckboxField( newValue ) { - props.setAttributes( { checkboxField: newValue } ); - } - - function onChangeRadioField( newValue ) { - props.setAttributes( { radioField: newValue } ); - } - - function onChangeTextField( newValue ) { - props.setAttributes( { textField: newValue } ); - } - - function onChangeToggleField( newValue ) { - props.setAttributes( { toggleField: newValue } ); - } - - function onChangeSelectField( newValue ) { - props.setAttributes( { selectField: newValue } ); - } - - return el( - Fragment, - null, - el( - InspectorControls, - null, - el( - PanelBody, - { - title: 'Settings', - }, - el( CheckboxControl, { - heading: 'Checkbox Field', - label: 'Tick Me', - help: 'Additional help text', - checked: checkboxField, - onChange: onChangeCheckboxField, - } ), - el( RadioControl, { - label: 'Radio Field', - selected: radioField, - options: [ - { - label: 'Yes', - value: 'yes', - }, - { - label: 'No', - value: 'no', - }, - ], - onChange: onChangeRadioField, - } ), - el( TextControl, { - label: 'Text Field', - help: 'Additional help text', - value: textField, - onChange: onChangeTextField, - } ), - el( ToggleControl, { - label: 'Toggle Field', - checked: toggleField, - onChange: onChangeToggleField, - } ), - el( SelectControl, { - label: 'Select Field', - value: selectField, - options: [ - { - value: 'a', - label: 'Option A', - }, - { - value: 'b', - label: 'Option B', - }, - { - value: 'c', - label: 'Option C', - }, - ], - onChange: onChangeSelectField, - } ) - ) - ), - el( - RichText, - Object.assing( blockProps, { - key: 'editable', - tagName: 'p', - onChange: onChangeContent, - value: content, - } ) - ) - ); - }, - - save: function ( props ) { - var blockProps = useBlockProps.save(); - var content = props.attributes.content, - checkboxField = props.attributes.checkboxField, - radioField = props.attributes.radioField, - textField = props.attributes.textField, - toggleField = props.attributes.toggleField, - selectField = props.attributes.selectField; - - return el( - 'div', - blockProps, - el( RichText.Content, { - value: content, - tagName: 'p', - } ), - el( 'h2', null, 'Inspector Control Fields' ), - el( - 'ul', - null, - el( 'li', null, 'Checkbox Field: ', checkboxField ), - el( 'li', null, 'Radio Field: ', radioField ), - el( 'li', null, 'Text Field: ', textField ), - el( 'li', null, 'Toggle Field: ', toggleField ), - el( 'li', null, 'Select Field: ', selectField ) - ) - ); - }, -} ); -``` - -{% ESNext %} - ```js import { registerBlockType } from '@wordpress/blocks'; import { @@ -209,7 +14,7 @@ import { TextControl, ToggleControl, SelectControl, - PanelBody + PanelBody, } from '@wordpress/components'; import { RichText, @@ -289,7 +94,7 @@ registerBlockType( 'my-plugin/inspector-controls-example', { return ( <> - + + +Inspector Advanced Controls appear under the _Advanced_ panel of a block's [InspectorControls](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inspector-controls/README.md) -- that is, they appear as a specific set of controls within a block's settings panels. As the name suggests, `InspectorAdvancedControls` is meant for controls that most users aren't meant to interact with most of the time, such as adding an HTML anchor or custom CSS classes to a block. + +### Usage + +```js +import { + TextControl, +} from '@wordpress/components'; +import { + InspectorControls, + InspectorAdvancedControls, +} from '@wordpress/block-editor'; + +function MyBlockEdit( { attributes, setAttributes } ) { + return ( + <> +
+ { /* Block markup goes here */ } +
+ { /* Regular control goes here */ +
+ + { + setAttributes( { + anchor: nextValue, + } ); + } } + /> + + + ); +} +``` diff --git a/packages/block-editor/src/components/inspector-controls/fill.js b/packages/block-editor/src/components/inspector-controls/fill.js new file mode 100644 index 00000000000000..bb1c8fd7accdf3 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls/fill.js @@ -0,0 +1,32 @@ +/** + * WordPress dependencies + */ +import { __experimentalStyleProvider as StyleProvider } from '@wordpress/components'; +import warning from '@wordpress/warning'; + +/** + * Internal dependencies + */ +import useDisplayBlockControls from '../use-display-block-controls'; +import groups from './groups'; + +export default function InspectorControlsFill( { + __experimentalGroup: group = 'default', + children, +} ) { + const isDisplayed = useDisplayBlockControls(); + const Fill = groups[ group ]?.Fill; + if ( ! Fill ) { + warning( `Unknown InspectorControl group "${ group }" provided.` ); + return null; + } + if ( ! isDisplayed ) { + return null; + } + + return ( + + { children } + + ); +} diff --git a/packages/block-editor/src/components/inspector-controls/index.native.js b/packages/block-editor/src/components/inspector-controls/fill.native.js similarity index 57% rename from packages/block-editor/src/components/inspector-controls/index.native.js rename to packages/block-editor/src/components/inspector-controls/fill.native.js index ed131ddb4ce6a7..01ac32b2565007 100644 --- a/packages/block-editor/src/components/inspector-controls/index.native.js +++ b/packages/block-editor/src/components/inspector-controls/fill.native.js @@ -7,18 +7,27 @@ import { View } from 'react-native'; * WordPress dependencies */ import { Children } from '@wordpress/element'; -import { createSlotFill, BottomSheetConsumer } from '@wordpress/components'; +import { BottomSheetConsumer } from '@wordpress/components'; +import warning from '@wordpress/warning'; /** * Internal dependencies */ +import groups from './groups'; import { useBlockEditContext } from '../block-edit/context'; import { BlockSettingsButton } from '../block-settings'; -const { Fill, Slot } = createSlotFill( 'InspectorControls' ); - -const FillWithSettingsButton = ( { children, ...props } ) => { +export default function InspectorControlsFill( { + children, + __experimentalGroup: group = 'default', + ...props +} ) { const { isSelected } = useBlockEditContext(); + const Fill = groups[ group ]?.Fill; + if ( ! Fill ) { + warning( `Unknown InspectorControl group "${ group }" provided.` ); + return null; + } if ( ! isSelected ) { return null; } @@ -35,13 +44,4 @@ const FillWithSettingsButton = ( { children, ...props } ) => { { Children.count( children ) > 0 && } ); -}; - -const InspectorControls = FillWithSettingsButton; - -InspectorControls.Slot = Slot; - -/** - * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inspector-controls/README.md - */ -export default InspectorControls; +} diff --git a/packages/block-editor/src/components/inspector-controls/groups.js b/packages/block-editor/src/components/inspector-controls/groups.js new file mode 100644 index 00000000000000..a989132afd4c72 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls/groups.js @@ -0,0 +1,14 @@ +/** + * WordPress dependencies + */ +import { createSlotFill } from '@wordpress/components'; + +const InspectorControlsDefault = createSlotFill( 'InspectorControls' ); +const InspectorControlsAdvanced = createSlotFill( 'InspectorAdvancedControls' ); + +const groups = { + default: InspectorControlsDefault, + advanced: InspectorControlsAdvanced, +}; + +export default groups; diff --git a/packages/block-editor/src/components/inspector-controls/index.js b/packages/block-editor/src/components/inspector-controls/index.js index 9a6cb7bab61a7d..4cb36cdc843994 100644 --- a/packages/block-editor/src/components/inspector-controls/index.js +++ b/packages/block-editor/src/components/inspector-controls/index.js @@ -1,27 +1,25 @@ -/** - * WordPress dependencies - */ -import { - __experimentalStyleProvider as StyleProvider, - createSlotFill, -} from '@wordpress/components'; - /** * Internal dependencies */ -import useDisplayBlockControls from '../use-display-block-controls'; +import InspectorControlsFill from './fill'; +import InspectorControlsSlot from './slot'; -const { Fill, Slot } = createSlotFill( 'InspectorControls' ); +const InspectorControls = InspectorControlsFill; -function InspectorControls( { children } ) { - return useDisplayBlockControls() ? ( - - { children } - - ) : null; -} +InspectorControls.Slot = InspectorControlsSlot; -InspectorControls.Slot = Slot; +// This is just here for backward compatibility. +export const InspectorAdvancedControls = ( props ) => { + return ( + + ); +}; +InspectorAdvancedControls.Slot = ( props ) => { + return ( + + ); +}; +InspectorAdvancedControls.slotName = 'InspectorAdvancedControls'; /** * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inspector-controls/README.md diff --git a/packages/block-editor/src/components/inspector-controls/slot.js b/packages/block-editor/src/components/inspector-controls/slot.js new file mode 100644 index 00000000000000..f17714cfe41b61 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls/slot.js @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { __experimentalUseSlot as useSlot } from '@wordpress/components'; +import warning from '@wordpress/warning'; + +/** + * Internal dependencies + */ +import groups from './groups'; + +export default function InspectorControlsSlot( { + __experimentalGroup: group = 'default', + bubblesVirtually = true, + ...props +} ) { + const Slot = groups[ group ]?.Slot; + const slot = useSlot( Slot?.__unstableName ); + if ( ! Slot || ! slot ) { + warning( `Unknown InspectorControl group "${ group }" provided.` ); + return null; + } + + const hasFills = Boolean( slot.fills && slot.fills.length ); + if ( ! hasFills ) { + return null; + } + + return ; +} diff --git a/packages/block-editor/src/components/inspector-controls/slot.native.js b/packages/block-editor/src/components/inspector-controls/slot.native.js new file mode 100644 index 00000000000000..adf4da06965e42 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls/slot.native.js @@ -0,0 +1,22 @@ +/** + * WordPress dependencies + */ +import warning from '@wordpress/warning'; + +/** + * Internal dependencies + */ +import groups from './groups'; + +export default function InspectorControlsSlot( { + __experimentalGroup: group = 'default', + ...props +} ) { + const Slot = groups[ group ]?.Slot; + if ( ! Slot ) { + warning( `Unknown InspectorControl group "${ group }" provided.` ); + return null; + } + + return ; +} diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index 3e1e2b813072ef..07024b78c51f66 100644 --- a/packages/block-editor/src/hooks/anchor.js +++ b/packages/block-editor/src/hooks/anchor.js @@ -16,7 +16,7 @@ import { Platform } from '@wordpress/element'; /** * Internal dependencies */ -import { InspectorControls, InspectorAdvancedControls } from '../components'; +import { InspectorControls } from '../components'; /** * Regular expression matching invalid anchor characters for replacement. @@ -107,9 +107,9 @@ export const withInspectorControl = createHigherOrderComponent( <> { isWeb && ( - + { textControl } - + ) } { /* * We plan to remove scoping anchors to 'core/heading' to support diff --git a/packages/block-editor/src/hooks/custom-class-name.js b/packages/block-editor/src/hooks/custom-class-name.js index ae5b623887cca0..0ed09f8975c5cb 100644 --- a/packages/block-editor/src/hooks/custom-class-name.js +++ b/packages/block-editor/src/hooks/custom-class-name.js @@ -15,7 +15,7 @@ import { createHigherOrderComponent } from '@wordpress/compose'; /** * Internal dependencies */ -import { InspectorAdvancedControls } from '../components'; +import { InspectorControls } from '../components'; /** * Filters registered block settings, extending attributes with anchor using ID @@ -59,7 +59,7 @@ export const withInspectorControl = createHigherOrderComponent( return ( <> - + - + ); } diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index c0f80bc40ae0d2..fd7bb6edde87c3 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -20,7 +20,6 @@ import { import { BlockControls, InspectorControls, - InspectorAdvancedControls, RichText, useBlockProps, __experimentalUseBorderProps as useBorderProps, @@ -260,13 +259,13 @@ function ButtonEdit( props ) { setAttributes={ setAttributes } /> - + - + ); } diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index 5628ea0105ce8d..c82ab09a1709af 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -5,7 +5,7 @@ import { useSelect } from '@wordpress/data'; import { InnerBlocks, useBlockProps, - InspectorAdvancedControls, + InspectorControls, __experimentalUseInnerBlocksProps as useInnerBlocksProps, useSetting, store as blockEditorStore, @@ -45,7 +45,7 @@ function GroupEdit( { attributes, setAttributes, clientId } ) { return ( <> - + - + { themeSupportsLayout && } { /* Ideally this is not needed but it's there for backward compatibility reason to keep this div for themes that might rely on its presence */ } diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 54b552186e7ffe..b4cc2c29f6252a 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -21,7 +21,6 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { BlockControls, InspectorControls, - InspectorAdvancedControls, RichText, __experimentalImageSizeControl as ImageSizeControl, __experimentalImageURLInputUI as ImageURLInputUI, @@ -371,7 +370,7 @@ export default function Image( { /> - + } /> - + ); diff --git a/packages/block-library/src/post-terms/edit.js b/packages/block-library/src/post-terms/edit.js index 4a939e2af579cd..4ce94235615a4f 100644 --- a/packages/block-library/src/post-terms/edit.js +++ b/packages/block-library/src/post-terms/edit.js @@ -8,7 +8,7 @@ import classnames from 'classnames'; */ import { AlignmentToolbar, - InspectorAdvancedControls, + InspectorControls, BlockControls, Warning, useBlockProps, @@ -79,7 +79,7 @@ export default function PostTermsEdit( { } } /> - + - +
{ isLoading && } { ! isLoading && diff --git a/packages/block-library/src/query/edit/index.js b/packages/block-library/src/query/edit/index.js index e297534ed8f797..b08bd12eba31bb 100644 --- a/packages/block-library/src/query/edit/index.js +++ b/packages/block-library/src/query/edit/index.js @@ -7,7 +7,7 @@ import { useInstanceId } from '@wordpress/compose'; import { useEffect } from '@wordpress/element'; import { BlockControls, - InspectorAdvancedControls, + InspectorControls, useBlockProps, useSetting, store as blockEditorStore, @@ -104,7 +104,7 @@ export function QueryContent( { attributes, setAttributes } ) { setDisplayLayout={ updateDisplayLayout } /> - + - + ); diff --git a/packages/block-library/src/template-part/edit/advanced-controls.js b/packages/block-library/src/template-part/edit/advanced-controls.js index 9086b307b1a839..26393db2d75858 100644 --- a/packages/block-library/src/template-part/edit/advanced-controls.js +++ b/packages/block-library/src/template-part/edit/advanced-controls.js @@ -4,7 +4,7 @@ import { useEntityProp } from '@wordpress/core-data'; import { SelectControl, TextControl } from '@wordpress/components'; import { sprintf, __ } from '@wordpress/i18n'; -import { InspectorAdvancedControls } from '@wordpress/block-editor'; +import { InspectorControls } from '@wordpress/block-editor'; import { useSelect } from '@wordpress/data'; export function TemplatePartAdvancedControls( { @@ -44,7 +44,7 @@ export function TemplatePartAdvancedControls( { }, [] ); return ( - + { isEntityAvailable && ( <> setAttributes( { tagName: value } ) } /> - + ); } From 1f92131317ac5889b61cb1e5d885fc54c4a6eceb Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Mon, 23 Aug 2021 09:52:33 +0200 Subject: [PATCH 008/214] Update changelog files --- packages/components/CHANGELOG.md | 11 ++++++++--- packages/components/package.json | 2 +- packages/eslint-plugin/CHANGELOG.md | 7 ++++++- packages/eslint-plugin/package.json | 2 +- .../CHANGELOG.md | 4 +++- .../package.json | 2 +- packages/scripts/CHANGELOG.md | 2 ++ packages/scripts/package.json | 2 +- 8 files changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 2ed797b1a56a7d..752245898aa451 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -4,16 +4,21 @@ ### Bug Fix -- Listen to `resize` events correctly in `useBreakpointIndex`. This hook is used in `useResponsiveValue` and consequently in the `Flex` and `Grid` components ([#33902](https://github.com/WordPress/gutenberg/pull/33902)) - Fixed RTL styles in `Flex` component ([#33729](https://github.com/WordPress/gutenberg/pull/33729)). +## 16.0.0 (2021-08-23) + ### Breaking Change -- Updated the visual styles of the RangeControl component ([#33824](https://github.com/WordPress/gutenberg/pull/33824)) +- Updated the visual styles of the RangeControl component ([#33824](https://github.com/WordPress/gutenberg/pull/33824)). ### New Feature -- Add `hideLabelFromVision` prop to `RangeControl` ([#33714](https://github.com/WordPress/gutenberg/pull/33714)) +- Add `hideLabelFromVision` prop to `RangeControl` ([#33714](https://github.com/WordPress/gutenberg/pull/33714)). + +### Bug Fix + +- Listen to `resize` events correctly in `useBreakpointIndex`. This hook is used in `useResponsiveValue` and consequently in the `Flex` and `Grid` components ([#33902](https://github.com/WordPress/gutenberg/pull/33902)) ## 15.0.0 (2021-07-29) diff --git a/packages/components/package.json b/packages/components/package.json index 8af8b32f533b10..71a9e6603ae50b 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "15.0.0", + "version": "16.0.0-prerelease", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 587e5a10fd1648..5b8830ff10c976 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -4,9 +4,14 @@ ### Bug Fix -- Include `.jsx` extension when linting import statements in case TypeScript not present ([#33746](https://github.com/WordPress/gutenberg/pull/33746)). - The recommended configuration will now respect `type` imports in TypeScript files ([#34055](https://github.com/WordPress/gutenberg/pull/34055)). +## 9.1.1 (2021-08-23) + +### Bug Fix + +- Include `.jsx` extension when linting import statements in case TypeScript not present ([#33746](https://github.com/WordPress/gutenberg/pull/33746)). + ## 9.1.0 (2021-07-21) ### Enhancement diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index a80b690be4276c..306177e4aef3a6 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/eslint-plugin", - "version": "9.1.0", + "version": "9.1.1-prerelease", "description": "ESLint plugin for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/library-export-default-webpack-plugin/CHANGELOG.md b/packages/library-export-default-webpack-plugin/CHANGELOG.md index f95007336313a7..dce16c429d1d84 100644 --- a/packages/library-export-default-webpack-plugin/CHANGELOG.md +++ b/packages/library-export-default-webpack-plugin/CHANGELOG.md @@ -2,9 +2,11 @@ ## Unreleased +## 2.2.0 (2021-08-23) + ### Deprecations -- This plugin is deprecated for webpack 5. Please use [`output.library.export`](https://webpack.js.org/configuration/output/#outputlibraryexport) instead ([#33818](ttps://github.com/WordPress/gutenberg/pull/33818)). +- This plugin is deprecated for webpack 5. Please use [`output.library.export`](https://webpack.js.org/configuration/output/#outputlibraryexport) instead ([#33818](https://github.com/WordPress/gutenberg/pull/33818)). ## 2.0.0 (2021-01-21) diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index f7585909f5dfec..f9a36565fb2ae1 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/library-export-default-webpack-plugin", - "version": "2.1.0", + "version": "2.2.0-prerelease", "description": "Webpack plugin for exporting default property for selected libraries.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 72acb296712ac7..adaf85dc90af5a 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 18.0.0 (2021-08-23) + ### Breaking Changes - Increase the minimum Node.js version to v12.13 matching requirements from bundled dependencies ([#33818](https://github.com/WordPress/gutenberg/pull/33818)). diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 561816a693aa9c..367f3424e5daa0 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "17.1.0", + "version": "18.0.0-prerelease", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From d78f324141cd26b4290671583163c2c9717746df Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Mon, 23 Aug 2021 09:54:08 +0200 Subject: [PATCH 009/214] chore(release): publish - @wordpress/annotations@2.2.2 - @wordpress/block-directory@3.0.1 - @wordpress/block-editor@7.0.1 - @wordpress/block-library@5.0.1 - @wordpress/blocks@11.0.1 - @wordpress/components@16.0.0 - @wordpress/compose@5.0.1 - @wordpress/core-data@4.0.1 - @wordpress/customize-widgets@2.0.1 - @wordpress/data-controls@2.2.2 - @wordpress/data@6.0.1 - @wordpress/dom@3.2.2 - @wordpress/e2e-test-utils@5.4.2 - @wordpress/e2e-tests@2.4.1 - @wordpress/edit-post@5.0.1 - @wordpress/edit-site@3.0.1 - @wordpress/edit-widgets@3.0.1 - @wordpress/editor@11.0.1 - @wordpress/eslint-plugin@9.1.1 - @wordpress/format-library@3.0.1 - @wordpress/icons@5.0.1 - @wordpress/interface@4.0.1 - @wordpress/keyboard-shortcuts@3.0.1 - @wordpress/library-export-default-webpack-plugin@2.2.0 - @wordpress/list-reusable-blocks@3.0.1 - @wordpress/notices@3.2.2 - @wordpress/nux@5.0.1 - @wordpress/plugins@4.0.1 - @wordpress/readable-js-assets-webpack-plugin@1.0.2 - @wordpress/reusable-blocks@3.0.1 - @wordpress/rich-text@5.0.1 - @wordpress/scripts@18.0.0 - @wordpress/server-side-render@3.0.1 - @wordpress/viewport@4.0.1 - @wordpress/widgets@2.0.1 --- packages/annotations/package.json | 2 +- packages/block-directory/package.json | 2 +- packages/block-editor/package.json | 2 +- packages/block-library/package.json | 2 +- packages/blocks/package.json | 2 +- packages/components/package.json | 2 +- packages/compose/package.json | 2 +- packages/core-data/package.json | 2 +- packages/customize-widgets/package.json | 2 +- packages/data-controls/package.json | 2 +- packages/data/package.json | 2 +- packages/dom/package.json | 2 +- packages/e2e-test-utils/package.json | 2 +- packages/e2e-tests/package.json | 2 +- packages/edit-post/package.json | 2 +- packages/edit-site/package.json | 2 +- packages/edit-widgets/package.json | 2 +- packages/editor/package.json | 2 +- packages/eslint-plugin/package.json | 2 +- packages/format-library/package.json | 2 +- packages/icons/package.json | 2 +- packages/interface/package.json | 2 +- packages/keyboard-shortcuts/package.json | 2 +- packages/library-export-default-webpack-plugin/package.json | 2 +- packages/list-reusable-blocks/package.json | 2 +- packages/notices/package.json | 2 +- packages/nux/package.json | 2 +- packages/plugins/package.json | 2 +- packages/readable-js-assets-webpack-plugin/package.json | 2 +- packages/reusable-blocks/package.json | 2 +- packages/rich-text/package.json | 2 +- packages/scripts/package.json | 2 +- packages/server-side-render/package.json | 2 +- packages/viewport/package.json | 2 +- packages/widgets/package.json | 2 +- 35 files changed, 35 insertions(+), 35 deletions(-) diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 95244ac008b6a0..b53046ace2124b 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "2.2.1", + "version": "2.2.2", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index f33aa6099bfa94..ce59cc46bb0619 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-directory", - "version": "3.0.0", + "version": "3.0.1", "description": "Extend editor with block directory features to search, download and install blocks.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 0550430cde029d..63a1cd006a4597 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-editor", - "version": "7.0.0", + "version": "7.0.1", "description": "Generic block editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/package.json b/packages/block-library/package.json index d02fee6ab2f616..4ad410cac248dc 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "5.0.0", + "version": "5.0.1", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 74c285fa650abe..8741fb317c907e 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "11.0.0", + "version": "11.0.1", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/components/package.json b/packages/components/package.json index 71a9e6603ae50b..c4679b1a6485fe 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "16.0.0-prerelease", + "version": "16.0.0", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/compose/package.json b/packages/compose/package.json index 7f9e47021c454a..f97c1131b59c47 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/compose", - "version": "5.0.0", + "version": "5.0.1", "description": "WordPress higher-order components (HOCs).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 70cabd048f0518..557aa1c182e9fd 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "4.0.0", + "version": "4.0.1", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/customize-widgets/package.json b/packages/customize-widgets/package.json index 1be81999fee62d..ae7260f507fe35 100644 --- a/packages/customize-widgets/package.json +++ b/packages/customize-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/customize-widgets", - "version": "2.0.0", + "version": "2.0.1", "description": "Widgets blocks in Customizer Module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json index 7407d97bfb2c04..4b132809bea526 100644 --- a/packages/data-controls/package.json +++ b/packages/data-controls/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data-controls", - "version": "2.2.1", + "version": "2.2.2", "description": "A set of common controls for the @wordpress/data api.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data/package.json b/packages/data/package.json index ddd920360f30e3..7d3fa835579db7 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "6.0.0", + "version": "6.0.1", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom/package.json b/packages/dom/package.json index 3adff09388c9e9..918415ad3d5fe7 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom", - "version": "3.2.1", + "version": "3.2.2", "description": "DOM utilities module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index 204f11b26901b9..e6ac75c7bbb069 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils", - "version": "5.4.1", + "version": "5.4.2", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index d7e4331cd695b6..479f2331230e76 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "2.4.0", + "version": "2.4.1", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index b5fe7f1c5e301c..6919bb5683d1a0 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "5.0.0", + "version": "5.0.1", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 955d1a94dd569d..988c8fce821c88 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-site", - "version": "3.0.0", + "version": "3.0.1", "description": "Edit Site Page module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index d898501b6b21c5..4d753c74255c9c 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-widgets", - "version": "3.0.0", + "version": "3.0.1", "description": "Widgets Page module for WordPress..", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/package.json b/packages/editor/package.json index 4bed95f0740bfe..069d27437c8c33 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "11.0.0", + "version": "11.0.1", "description": "Enhanced block editor for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 306177e4aef3a6..ed0b6d76cea633 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/eslint-plugin", - "version": "9.1.1-prerelease", + "version": "9.1.1", "description": "ESLint plugin for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/package.json b/packages/format-library/package.json index 52fdb9028d451b..68a8b2060f5973 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "3.0.0", + "version": "3.0.1", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/icons/package.json b/packages/icons/package.json index 200d3a701139f9..463ad625b130ed 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/icons", - "version": "5.0.0", + "version": "5.0.1", "description": "WordPress Icons package, based on dashicon.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/interface/package.json b/packages/interface/package.json index ee0e53bae44b69..7880c08042ea64 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/interface", - "version": "4.0.0", + "version": "4.0.1", "description": "Interface module for WordPress. The package contains shared functionality across the modern JavaScript-based WordPress screens.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/keyboard-shortcuts/package.json b/packages/keyboard-shortcuts/package.json index d44c89ddf24f50..007167b069f227 100644 --- a/packages/keyboard-shortcuts/package.json +++ b/packages/keyboard-shortcuts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/keyboard-shortcuts", - "version": "3.0.0", + "version": "3.0.1", "description": "Handling keyboard shortcuts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index f9a36565fb2ae1..5f3cedd95c103d 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/library-export-default-webpack-plugin", - "version": "2.2.0-prerelease", + "version": "2.2.0", "description": "Webpack plugin for exporting default property for selected libraries.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 9d7699f1b1957d..06cb297ceda026 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "3.0.0", + "version": "3.0.1", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/notices/package.json b/packages/notices/package.json index 3abcea38ad2ab8..d781ee3dcf84eb 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/notices", - "version": "3.2.1", + "version": "3.2.2", "description": "State management for notices.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/package.json b/packages/nux/package.json index 28a704db1cff2d..f8297a55a3727c 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "5.0.0", + "version": "5.0.1", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/plugins/package.json b/packages/plugins/package.json index cf95ff899d3ae5..3145b2539a19db 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/plugins", - "version": "4.0.0", + "version": "4.0.1", "description": "Plugins module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/readable-js-assets-webpack-plugin/package.json b/packages/readable-js-assets-webpack-plugin/package.json index 5689f99182ed60..54cf4f78a2940b 100644 --- a/packages/readable-js-assets-webpack-plugin/package.json +++ b/packages/readable-js-assets-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/readable-js-assets-webpack-plugin", - "version": "1.0.1", + "version": "1.0.2", "description": "Generate a readable JS file for each JS asset.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/reusable-blocks/package.json b/packages/reusable-blocks/package.json index d259a1b808ba66..f900c6fea1d978 100644 --- a/packages/reusable-blocks/package.json +++ b/packages/reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/reusable-blocks", - "version": "3.0.0", + "version": "3.0.1", "description": "Reusable blocks utilities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 20a2613b082283..a82b4ed4200186 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "5.0.0", + "version": "5.0.1", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 367f3424e5daa0..78c2bc8102e6bd 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "18.0.0-prerelease", + "version": "18.0.0", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/server-side-render/package.json b/packages/server-side-render/package.json index 31f439d5eedb5c..04e667999c63b7 100644 --- a/packages/server-side-render/package.json +++ b/packages/server-side-render/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/server-side-render", - "version": "3.0.0", + "version": "3.0.1", "description": "The component used with WordPress to server-side render a preview of dynamic blocks to display in the editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 8f051774e39c4a..7a0271bea68eb5 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/viewport", - "version": "4.0.0", + "version": "4.0.1", "description": "Viewport module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/widgets/package.json b/packages/widgets/package.json index ae95b3f872ad77..2934f8442b10f9 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/widgets", - "version": "2.0.0", + "version": "2.0.1", "description": "Functionality used by the widgets block editor in the Widgets screen and the Customizer.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 292d9f2f8752c0aec610113f7f893739e32021e4 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Mon, 23 Aug 2021 16:59:30 +0800 Subject: [PATCH 010/214] Migrate post editor feature preferences to the interface package (#34154) * Migrate post editor feature preferences to the interface package * Remove snapshot for MoreMenu * Remove tests for actions/selectors that now call through to the interface store * Remove reducer test --- .../data/data-core-edit-post.md | 6 +- .../data/src/plugins/persistence/index.js | 1 + .../components/header/feature-toggle/index.js | 61 --------- .../src/components/header/more-menu/index.js | 21 ++- .../test/__snapshots__/index.js.snap | 129 ------------------ .../components/header/more-menu/test/index.js | 17 --- .../components/header/writing-menu/index.js | 15 +- packages/edit-post/src/index.js | 13 ++ .../plugins/welcome-guide-menu-item/index.js | 19 +-- packages/edit-post/src/store/actions.js | 16 +-- packages/edit-post/src/store/defaults.js | 9 -- packages/edit-post/src/store/reducer.js | 10 -- packages/edit-post/src/store/selectors.js | 11 +- packages/edit-post/src/store/test/actions.js | 11 -- packages/edit-post/src/store/test/reducer.js | 12 -- .../edit-post/src/store/test/selectors.js | 46 ------- 16 files changed, 53 insertions(+), 344 deletions(-) delete mode 100644 packages/edit-post/src/components/header/feature-toggle/index.js delete mode 100644 packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap delete mode 100644 packages/edit-post/src/components/header/more-menu/test/index.js diff --git a/docs/reference-guides/data/data-core-edit-post.md b/docs/reference-guides/data/data-core-edit-post.md index 2348d6544020da..e8dd31780a3ad6 100644 --- a/docs/reference-guides/data/data-core-edit-post.md +++ b/docs/reference-guides/data/data-core-edit-post.md @@ -502,16 +502,12 @@ _Returns_ ### toggleFeature -Returns an action object used to toggle a feature flag. +Triggers an action used to toggle a feature flag. _Parameters_ - _feature_ `string`: Feature name. -_Returns_ - -- `Object`: Action object. - ### togglePinnedPluginItem Triggers an action object used to toggle a plugin name flag. diff --git a/packages/data/src/plugins/persistence/index.js b/packages/data/src/plugins/persistence/index.js index f22e135100f166..24baf115aa1dcc 100644 --- a/packages/data/src/plugins/persistence/index.js +++ b/packages/data/src/plugins/persistence/index.js @@ -285,6 +285,7 @@ persistencePlugin.__unstableMigrate = ( pluginOptions ) => { persistence, 'core/customize-widgets' ); + migrateFeaturePreferencesToInterfaceStore( persistence, 'core/edit-post' ); }; export default persistencePlugin; diff --git a/packages/edit-post/src/components/header/feature-toggle/index.js b/packages/edit-post/src/components/header/feature-toggle/index.js deleted file mode 100644 index 13f72ae8d75a4e..00000000000000 --- a/packages/edit-post/src/components/header/feature-toggle/index.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * External dependencies - */ -import { flow } from 'lodash'; - -/** - * WordPress dependencies - */ -import { withSelect, withDispatch } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; -import { MenuItem } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { check } from '@wordpress/icons'; -import { speak } from '@wordpress/a11y'; - -/** - * Internal dependencies - */ -import { store as editPostStore } from '../../../store'; - -function FeatureToggle( { - onToggle, - isActive, - label, - info, - messageActivated, - messageDeactivated, - shortcut, -} ) { - const speakMessage = () => { - if ( isActive ) { - speak( messageDeactivated || __( 'Feature deactivated' ) ); - } else { - speak( messageActivated || __( 'Feature activated' ) ); - } - }; - - return ( - - { label } - - ); -} - -export default compose( [ - withSelect( ( select, { feature } ) => ( { - isActive: select( editPostStore ).isFeatureActive( feature ), - } ) ), - withDispatch( ( dispatch, ownProps ) => ( { - onToggle() { - dispatch( editPostStore ).toggleFeature( ownProps.feature ); - }, - } ) ), -] )( FeatureToggle ); diff --git a/packages/edit-post/src/components/header/more-menu/index.js b/packages/edit-post/src/components/header/more-menu/index.js index 17b763b26f3749..ed06ba47259996 100644 --- a/packages/edit-post/src/components/header/more-menu/index.js +++ b/packages/edit-post/src/components/header/more-menu/index.js @@ -2,9 +2,12 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { DropdownMenu, MenuGroup } from '@wordpress/components'; -import { moreVertical } from '@wordpress/icons'; -import { ActionItem, PinnedItems } from '@wordpress/interface'; +import { MenuGroup } from '@wordpress/components'; +import { + ActionItem, + MoreMenuDropdown, + PinnedItems, +} from '@wordpress/interface'; import { useViewportMatch } from '@wordpress/compose'; /** @@ -17,26 +20,18 @@ import WritingMenu from '../writing-menu'; const POPOVER_PROPS = { className: 'edit-post-more-menu__content', - position: 'bottom left', -}; -const TOGGLE_PROPS = { - tooltipPosition: 'bottom', }; const MoreMenu = ( { showIconLabels } ) => { const isLargeViewport = useViewportMatch( 'large' ); return ( - { ( { onClose } ) => ( @@ -61,7 +56,7 @@ const MoreMenu = ( { showIconLabels } ) => { ) } - + ); }; diff --git a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap deleted file mode 100644 index 9ffdb5a699ff04..00000000000000 --- a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,129 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MoreMenu should match snapshot 1`] = ` - - - - - } - label="Options" - popoverProps={ - Object { - "className": "edit-post-more-menu__content", - "position": "bottom left", - } - } - toggleProps={ - Object { - "showTooltip": true, - "tooltipPosition": "bottom", - } - } - > - -
- - - - } - label="Options" - onClick={[Function]} - onKeyDown={[Function]} - showTooltip={true} - tooltipPosition="bottom" - > - - - - -
-
-
-
-`; diff --git a/packages/edit-post/src/components/header/more-menu/test/index.js b/packages/edit-post/src/components/header/more-menu/test/index.js deleted file mode 100644 index 2da96abe04ea4e..00000000000000 --- a/packages/edit-post/src/components/header/more-menu/test/index.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * External dependencies - */ -import { mount } from 'enzyme'; - -/** - * Internal dependencies - */ -import MoreMenu from '../index'; - -describe( 'MoreMenu', () => { - it( 'should match snapshot', () => { - const wrapper = mount( ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/edit-post/src/components/header/writing-menu/index.js b/packages/edit-post/src/components/header/writing-menu/index.js index 71d3ddb339d20b..bcd4edad123ce2 100644 --- a/packages/edit-post/src/components/header/writing-menu/index.js +++ b/packages/edit-post/src/components/header/writing-menu/index.js @@ -5,11 +5,7 @@ import { MenuGroup } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; import { useViewportMatch } from '@wordpress/compose'; import { displayShortcut } from '@wordpress/keycodes'; - -/** - * Internal dependencies - */ -import FeatureToggle from '../feature-toggle'; +import { MoreMenuFeatureToggle } from '@wordpress/interface'; function WritingMenu() { const isLargeViewport = useViewportMatch( 'medium' ); @@ -19,7 +15,8 @@ function WritingMenu() { return ( - - - select( editPostStore ).isEditingTemplate(), [] ); - const { toggleFeature } = useDispatch( editPostStore ); return ( - - toggleFeature( - isTemplateMode ? 'welcomeGuideTemplate' : 'welcomeGuide' - ) - } - > - { __( 'Welcome Guide' ) } - + ); } diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index 6cd238b06bdfde..d253d55e74efd7 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -153,17 +153,17 @@ export function removeEditorPanel( panelName ) { } /** - * Returns an action object used to toggle a feature flag. + * Triggers an action used to toggle a feature flag. * * @param {string} feature Feature name. - * - * @return {Object} Action object. */ -export function toggleFeature( feature ) { - return { - type: 'TOGGLE_FEATURE', - feature, - }; +export function* toggleFeature( feature ) { + yield controls.dispatch( + interfaceStore.name, + 'toggleFeature', + 'core/edit-post', + feature + ); } export function* switchEditorMode( mode ) { diff --git a/packages/edit-post/src/store/defaults.js b/packages/edit-post/src/store/defaults.js index 05cb4c8e1957a3..9bd5f366a02f9a 100644 --- a/packages/edit-post/src/store/defaults.js +++ b/packages/edit-post/src/store/defaults.js @@ -5,15 +5,6 @@ export const PREFERENCES_DEFAULTS = { opened: true, }, }, - features: { - fixedToolbar: false, - welcomeGuide: true, - fullscreenMode: true, - showIconLabels: false, - themeStyles: true, - showBlockBreadcrumbs: true, - welcomeGuideTemplate: true, - }, hiddenBlockTypes: [], preferredStyleVariations: {}, localAutosaveInterval: 15, diff --git a/packages/edit-post/src/store/reducer.js b/packages/edit-post/src/store/reducer.js index 2f9f0ec544bf0e..3441c613f5f619 100644 --- a/packages/edit-post/src/store/reducer.js +++ b/packages/edit-post/src/store/reducer.js @@ -78,16 +78,6 @@ export const preferences = flow( [ return state; }, - features( state, action ) { - if ( action.type === 'TOGGLE_FEATURE' ) { - return { - ...state, - [ action.feature ]: ! state[ action.feature ], - }; - } - - return state; - }, editorMode( state, action ) { if ( action.type === 'SWITCH_MODE' ) { return action.mode; diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index e8a8bddccbc168..2d40810a3cf569 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -189,9 +189,14 @@ export function isModalActive( state, modalName ) { * * @return {boolean} Is active. */ -export function isFeatureActive( state, feature ) { - return get( state.preferences.features, [ feature ], false ); -} +export const isFeatureActive = createRegistrySelector( + ( select ) => ( state, feature ) => { + return select( interfaceStore ).isFeatureActive( + 'core/edit-post', + feature + ); + } +); /** * Returns true if the plugin item is pinned to the header. diff --git a/packages/edit-post/src/store/test/actions.js b/packages/edit-post/src/store/test/actions.js index 9525fbd1ce8800..06739597fd9349 100644 --- a/packages/edit-post/src/store/test/actions.js +++ b/packages/edit-post/src/store/test/actions.js @@ -15,7 +15,6 @@ import { togglePublishSidebar, openModal, closeModal, - toggleFeature, requestMetaBoxUpdates, setIsListViewOpened, } from '../actions'; @@ -90,16 +89,6 @@ describe( 'actions', () => { } ); } ); - describe( 'toggleFeature', () => { - it( 'should return TOGGLE_FEATURE action', () => { - const feature = 'name'; - expect( toggleFeature( feature ) ).toEqual( { - type: 'TOGGLE_FEATURE', - feature, - } ); - } ); - } ); - describe( 'requestMetaBoxUpdates', () => { it( 'should yield the REQUEST_META_BOX_UPDATES action', () => { const fulfillment = requestMetaBoxUpdates(); diff --git a/packages/edit-post/src/store/test/reducer.js b/packages/edit-post/src/store/test/reducer.js index 9ad1574f8de35a..67b097e27a8dd0 100644 --- a/packages/edit-post/src/store/test/reducer.js +++ b/packages/edit-post/src/store/test/reducer.js @@ -152,18 +152,6 @@ describe( 'state', () => { expect( state.editorMode ).toBe( 'text' ); } ); - it( 'should toggle a feature flag', () => { - const state = preferences( - deepFreeze( { features: { chicken: true } } ), - { - type: 'TOGGLE_FEATURE', - feature: 'chicken', - } - ); - - expect( state.features ).toEqual( { chicken: false } ); - } ); - describe( 'hiddenBlockTypes', () => { it( 'concatenates unique names on disable', () => { const original = deepFreeze( { diff --git a/packages/edit-post/src/store/test/selectors.js b/packages/edit-post/src/store/test/selectors.js index 7ec6bf55546ba9..d9828eaa87e5a9 100644 --- a/packages/edit-post/src/store/test/selectors.js +++ b/packages/edit-post/src/store/test/selectors.js @@ -11,7 +11,6 @@ import { getPreference, isEditorPanelOpened, isModalActive, - isFeatureActive, hasMetaBoxes, isSavingMetaBoxes, getActiveMetaBoxLocations, @@ -239,51 +238,6 @@ describe( 'selectors', () => { } ); } ); - describe( 'isFeatureActive', () => { - it( 'is tolerant to an undefined features preference', () => { - // See: https://github.com/WordPress/gutenberg/issues/14580 - const state = { - preferences: {}, - }; - - expect( isFeatureActive( state, 'chicken' ) ).toBe( false ); - } ); - - it( 'should return true if feature is active', () => { - const state = { - preferences: { - features: { - chicken: true, - }, - }, - }; - - expect( isFeatureActive( state, 'chicken' ) ).toBe( true ); - } ); - - it( 'should return false if feature is not active', () => { - const state = { - preferences: { - features: { - chicken: false, - }, - }, - }; - - expect( isFeatureActive( state, 'chicken' ) ).toBe( false ); - } ); - - it( 'should return false if feature is not referred', () => { - const state = { - preferences: { - features: {}, - }, - }; - - expect( isFeatureActive( state, 'chicken' ) ).toBe( false ); - } ); - } ); - describe( 'hasMetaBoxes', () => { it( 'should return true if there are active meta boxes', () => { const state = { From 13efc3aadc8c7a694f81bb4576147d7978058b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Mon, 23 Aug 2021 13:43:33 +0200 Subject: [PATCH 011/214] Documentation: fix typo in block gap docs (#34231) --- docs/how-to-guides/themes/theme-json.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md index 460a43ba9161e2..b4151d83071f18 100644 --- a/docs/how-to-guides/themes/theme-json.md +++ b/docs/how-to-guides/themes/theme-json.md @@ -615,7 +615,6 @@ Each block declares which style properties it exposes via the [block supports me "text": "value" }, "spacing": { - "blockGap": "value", "margin": { "top": "value", "right": "value", From 9e3be448664646245c875baa01539a7bd96ee99a Mon Sep 17 00:00:00 2001 From: Daniel Walmsley Date: Mon, 23 Aug 2021 04:53:39 -0700 Subject: [PATCH 012/214] Remove deprecated import style for storybook/addon-docs (#34095) --- storybook/stories/docs/introduction.story.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storybook/stories/docs/introduction.story.mdx b/storybook/stories/docs/introduction.story.mdx index bf4756bac9a12e..55fb09f0a9f850 100644 --- a/storybook/stories/docs/introduction.story.mdx +++ b/storybook/stories/docs/introduction.story.mdx @@ -1,4 +1,4 @@ -import { Meta } from '@storybook/addon-docs/blocks'; +import { Meta } from '@storybook/addon-docs'; From 19e5da6c5d169699b445a65e29dd3e1d71c948bd Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Mon, 23 Aug 2021 10:09:24 -0400 Subject: [PATCH 013/214] simple fix (#34203) --- .../navigation-panel/content-navigation-item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/content-navigation-item.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/content-navigation-item.js index e33a4fba22ed50..43f460f26e367e 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/content-navigation-item.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/content-navigation-item.js @@ -36,7 +36,7 @@ export default function ContentNavigationItem( { item } ) { const template = select( coreStore ).__experimentalGetTemplateForLink( item.link ); - return template?.content?.raw; + return template?.content; }, [ isPreviewVisible ] ); From 208eda8d71837a5792086519467e30e7907aa878 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Mon, 23 Aug 2021 18:17:13 +0200 Subject: [PATCH 014/214] Fix vertical page list. (#34226) --- packages/block-library/src/navigation/style.scss | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index 2d3671c847a6c8..cf5a543e3e586b 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -338,13 +338,14 @@ // Horizontal layout display: flex; flex-wrap: wrap; +} - // Vertical layout - .is-vertical & { - display: block; - flex-direction: column; - align-items: flex-start; - } +// Vertical layout +.is-vertical .wp-block-page-list, // Page list. +.is-vertical .wp-block-navigation__container { + display: block; + flex-direction: column; + align-items: flex-start; } // Justification. From 518e3c80966be2e8ba8cf085d6ec824b39934fb4 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Mon, 23 Aug 2021 18:25:36 +0200 Subject: [PATCH 015/214] Try: Fix navigation responsive menu overlay z index. (#34228) --- packages/block-library/src/navigation/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index cf5a543e3e586b..bdc8183a235ea3 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -442,6 +442,7 @@ display: flex; flex-direction: row; position: relative; + z-index: 2; background-color: inherit; .wp-block-navigation__responsive-container-close { From c238f4324354a4a87293fe83ff1268ce7889e1c2 Mon Sep 17 00:00:00 2001 From: Jason Johnston Date: Mon, 23 Aug 2021 13:02:59 -0400 Subject: [PATCH 016/214] [RNMobile] Use Flexbox for Inserter Menu tabs on mobile (#34126) * Use flexbox for tab positioning * Make sure height is calculated correctly Co-authored-by: jhnstn --- .../block-editor/src/components/inserter/menu.native.js | 5 ++++- .../src/components/inserter/style.native.scss | 7 +++---- .../block-editor/src/components/inserter/tabs.native.js | 8 +------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js index 198bb761329e38..5f556714456129 100644 --- a/packages/block-editor/src/components/inserter/menu.native.js +++ b/packages/block-editor/src/components/inserter/menu.native.js @@ -204,7 +204,10 @@ function InserterMenu( { > { ( { listProps } ) => ( - + { ! showTabs || filterValue ? ( { tabs.map( ( { component: TabComponent }, index ) => ( - + Date: Mon, 23 Aug 2021 10:21:03 -0700 Subject: [PATCH 017/214] Rnmobile/add/typography controls (#33605) * Allow for a non units to show without the unit picker. * Add Line Height Controls * Make variable more clear * Revert Typography web * Add support for font sizes * Add bottom Cell sheet subLabel * Update the colour of the subheading * Update colours again for subheading * Make the Typography UI only available in dev build * Do not show items that we can't parse as numbers * Add comment * Move the FontSizePicker export * Remove the not needed platform check * Update comment to be more font size specific. * Fix unit test for customLineHeight * Revert the units logic to hide the Unit picker * Remove the added borderRightWidth for picker that doesn't have childern --- packages/base-styles/_colors.native.scss | 3 + .../src/components/font-sizes/index.native.js | 8 +- .../line-height-control/index.native.js | 25 +++ packages/block-editor/src/hooks/typography.js | 8 +- .../src/hooks/typography.native.js | 64 +++++++ .../src/font-size-picker/index.native.js | 173 ++++++++++++++++++ .../src/font-size-picker/style.native.scss | 6 + packages/components/src/index.native.js | 1 + .../src/mobile/bottom-sheet/cell.native.js | 23 ++- .../bottom-sheet/range-text-input.native.js | 1 + .../mobile/bottom-sheet/styles.native.scss | 9 + .../test/utils.native.js | 4 +- .../global-styles-context/utils.native.js | 4 +- .../src/unit-control/index.native.js | 4 + 14 files changed, 319 insertions(+), 14 deletions(-) create mode 100644 packages/block-editor/src/components/line-height-control/index.native.js create mode 100644 packages/block-editor/src/hooks/typography.native.js create mode 100644 packages/components/src/font-size-picker/index.native.js create mode 100644 packages/components/src/font-size-picker/style.native.scss diff --git a/packages/base-styles/_colors.native.scss b/packages/base-styles/_colors.native.scss index a34e5012eecf9c..ba76d005b622a3 100644 --- a/packages/base-styles/_colors.native.scss +++ b/packages/base-styles/_colors.native.scss @@ -53,6 +53,7 @@ $gray-text-min: darken($gray, 18%); //#537994 $gray-lighten-10: lighten($gray, 10%); // #a8bece $gray-lighten-20: lighten($gray, 20%); // #c8d7e1 $gray-lighten-30: lighten($gray, 30%); // #e9eff3 +$gray-darken-10: darken($gray, 10%); $gray-darken-20: darken($gray, 20%); // #4f748e $gray-darken-30: darken($gray, 30%); // #3d596d @@ -102,6 +103,8 @@ $app-background-dark-alt: $background-dark-elevated; $modal-background: $white; $modal-background-dark: $background-dark-elevated; +$sub-heading: $gray-text-min; +$sub-heading-dark: $white; /** * Deprecated colors. * Please avoid using these. diff --git a/packages/block-editor/src/components/font-sizes/index.native.js b/packages/block-editor/src/components/font-sizes/index.native.js index 2ebb67cf494ba1..55ef51a02aa4d5 100644 --- a/packages/block-editor/src/components/font-sizes/index.native.js +++ b/packages/block-editor/src/components/font-sizes/index.native.js @@ -1 +1,7 @@ -export { getFontSize, getFontSizeClass } from './utils'; +export { + getFontSize, + getFontSizeClass, + getFontSizeObjectByValue, +} from './utils'; +export { default as FontSizePicker } from './font-size-picker'; +export { default as withFontSizes } from './with-font-sizes'; diff --git a/packages/block-editor/src/components/line-height-control/index.native.js b/packages/block-editor/src/components/line-height-control/index.native.js new file mode 100644 index 00000000000000..95645805592cf1 --- /dev/null +++ b/packages/block-editor/src/components/line-height-control/index.native.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { UnitControl } from '@wordpress/components'; +/** + * Internal dependencies + */ +import { BASE_DEFAULT_VALUE, STEP, isLineHeightDefined } from './utils'; + +export default function LineHeightControl( { value: lineHeight, onChange } ) { + const isDefined = isLineHeightDefined( lineHeight ); + const value = isDefined ? lineHeight : BASE_DEFAULT_VALUE; + return ( + + ); +} diff --git a/packages/block-editor/src/hooks/typography.js b/packages/block-editor/src/hooks/typography.js index 1b961990acaaae..5bd009bb78dc3c 100644 --- a/packages/block-editor/src/hooks/typography.js +++ b/packages/block-editor/src/hooks/typography.js @@ -6,7 +6,6 @@ import { hasBlockSupport } from '@wordpress/blocks'; * External dependencies */ import { PanelBody } from '@wordpress/components'; -import { Platform } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -83,11 +82,8 @@ export function TypographyPanel( props ) { } const hasTypographySupport = ( blockName ) => { - return ( - Platform.OS === 'web' && - TYPOGRAPHY_SUPPORT_KEYS.some( ( key ) => - hasBlockSupport( blockName, key ) - ) + return TYPOGRAPHY_SUPPORT_KEYS.some( ( key ) => + hasBlockSupport( blockName, key ) ); }; diff --git a/packages/block-editor/src/hooks/typography.native.js b/packages/block-editor/src/hooks/typography.native.js new file mode 100644 index 00000000000000..396d48439f3a36 --- /dev/null +++ b/packages/block-editor/src/hooks/typography.native.js @@ -0,0 +1,64 @@ +/** + * WordPress dependencies + */ +import { hasBlockSupport } from '@wordpress/blocks'; +/** + * External dependencies + */ +import { PanelBody } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import InspectorControls from '../components/inspector-controls'; + +import { + LINE_HEIGHT_SUPPORT_KEY, + LineHeightEdit, + useIsLineHeightDisabled, +} from './line-height'; +import { + FONT_SIZE_SUPPORT_KEY, + FontSizeEdit, + useIsFontSizeDisabled, +} from './font-size'; + +export const TYPOGRAPHY_SUPPORT_KEY = 'typography'; +export const TYPOGRAPHY_SUPPORT_KEYS = [ + LINE_HEIGHT_SUPPORT_KEY, + FONT_SIZE_SUPPORT_KEY, +]; + +export function TypographyPanel( props ) { + const isDisabled = useIsTypographyDisabled( props ); + const isSupported = hasTypographySupport( props.name ); + + // only enable TypographyPanel for development + // eslint-disable-next-line no-undef + if ( isDisabled || ! isSupported || ! __DEV__ ) return null; + + return ( + + + + + + + ); +} + +const hasTypographySupport = ( blockName ) => { + return TYPOGRAPHY_SUPPORT_KEYS.some( ( key ) => + hasBlockSupport( blockName, key ) + ); +}; + +function useIsTypographyDisabled( props = {} ) { + const configs = [ + useIsFontSizeDisabled( props ), + useIsLineHeightDisabled( props ), + ]; + + return configs.filter( Boolean ).length === configs.length; +} diff --git a/packages/components/src/font-size-picker/index.native.js b/packages/components/src/font-size-picker/index.native.js new file mode 100644 index 00000000000000..2be833c450a82a --- /dev/null +++ b/packages/components/src/font-size-picker/index.native.js @@ -0,0 +1,173 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { useNavigation } from '@react-navigation/native'; +import { useState } from '@wordpress/element'; +import { Icon, chevronRight, check } from '@wordpress/icons'; +import { __, sprintf } from '@wordpress/i18n'; +import { BottomSheet } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { default as UnitControl, useCustomUnits } from '../unit-control'; +import styles from './style.scss'; + +function FontSizePicker( { + fontSizes = [], + disableCustomFontSizes = false, + onChange, + value: selectedValue, +} ) { + const [ showSubSheet, setShowSubSheet ] = useState( false ); + const navigation = useNavigation(); + + const onChangeValue = ( value ) => { + return () => { + goBack(); + onChange( value ); + }; + }; + + const selectedOption = fontSizes.find( + ( option ) => option.size === selectedValue + ) ?? { name: 'Custom' }; + + const goBack = () => { + setShowSubSheet( false ); + navigation.goBack(); + }; + + const openSubSheet = () => { + navigation.navigate( BottomSheet.SubSheet.screenName ); + setShowSubSheet( true ); + }; + const label = __( 'Font Size' ); + + const units = useCustomUnits( { + availableUnits: [ 'px', 'em', 'rem' ], + } ); + + return ( + + + + } + showSheet={ showSubSheet } + > + <> + + + + + { selectedValue === undefined && ( + + ) } + + + { fontSizes.map( ( item, index ) => { + // Only display a choice that we can currenly select. + if ( ! parseFloat( item.size ) ) { + return null; + } + return ( + + + { item.size === selectedValue && ( + + ) } + + + ); + } ) } + { ! disableCustomFontSizes && ( + { + if ( + 0 === parseFloat( nextSize ) || + ! nextSize + ) { + onChange( undefined ); + } else { + onChange( nextSize ); + } + } } + units={ units } + /> + ) } + + + + ); +} + +export default FontSizePicker; diff --git a/packages/components/src/font-size-picker/style.native.scss b/packages/components/src/font-size-picker/style.native.scss new file mode 100644 index 00000000000000..746e62b5bb3614 --- /dev/null +++ b/packages/components/src/font-size-picker/style.native.scss @@ -0,0 +1,6 @@ +.components-font-size-picker { + padding: 0 $block-edge-to-content; +} +.components-font-size-picker__font-size { + font-size: 11px; +} diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index e8aef964cc1a6b..626c3f03da0bae 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -30,6 +30,7 @@ export { Fill, Provider as SlotFillProvider, } from './slot-fill'; +export { default as FontSizePicker } from './font-size-picker'; // Intentionally called after slot-fill. export { default as __experimentalStyleProvider } from './style-provider'; export { default as BaseControl } from './base-control'; export { default as TextareaControl } from './textarea-control'; diff --git a/packages/components/src/mobile/bottom-sheet/cell.native.js b/packages/components/src/mobile/bottom-sheet/cell.native.js index ee5b7d0ace4c45..136e4daef1b220 100644 --- a/packages/components/src/mobile/bottom-sheet/cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/cell.native.js @@ -101,6 +101,7 @@ class BottomSheetCell extends Component { onPress, onLongPress, label, + subLabel, value, valuePlaceholder = '', icon, @@ -147,6 +148,11 @@ class BottomSheetCell extends Component { ? cellLabelStyle : defaultMissingIconAndValue; + const defaultSubLabelStyleText = getStylesFromColorScheme( + styles.cellSubLabelText, + styles.cellSubLabelTextDark + ); + const drawSeparator = ( separatorType && separatorType !== 'none' ) || separatorStyle === undefined; @@ -366,7 +372,22 @@ class BottomSheetCell extends Component { /> ) } - { label && ( + { subLabel && label && ( + + + { label } + + + { subLabel } + + + ) } + { ! subLabel && label && ( diff --git a/packages/components/src/mobile/bottom-sheet/range-text-input.native.js b/packages/components/src/mobile/bottom-sheet/range-text-input.native.js index 47885260653de8..91556f3cb834e4 100644 --- a/packages/components/src/mobile/bottom-sheet/range-text-input.native.js +++ b/packages/components/src/mobile/bottom-sheet/range-text-input.native.js @@ -194,6 +194,7 @@ class RangeTextInput extends Component { } ), { width: 50 * fontScale, + borderRightWidth: children ? 1 : 0, }, ]; diff --git a/packages/components/src/mobile/bottom-sheet/styles.native.scss b/packages/components/src/mobile/bottom-sheet/styles.native.scss index 7d60489fa4eac9..39ae11c027100f 100644 --- a/packages/components/src/mobile/bottom-sheet/styles.native.scss +++ b/packages/components/src/mobile/bottom-sheet/styles.native.scss @@ -304,3 +304,12 @@ .cellHelpLabelIOS { padding-bottom: $grid-unit-10; } + +.cellSubLabelText { + font-size: 12px; + color: $sub-heading; +} + +.cellSubLabelTextDark { + color: $sub-heading-dark; +} diff --git a/packages/components/src/mobile/global-styles-context/test/utils.native.js b/packages/components/src/mobile/global-styles-context/test/utils.native.js index 7b95e8a41a0c56..6a7f219118777a 100644 --- a/packages/components/src/mobile/global-styles-context/test/utils.native.js +++ b/packages/components/src/mobile/global-styles-context/test/utils.native.js @@ -134,9 +134,7 @@ describe( 'getGlobalStyles', () => { }, typography: { fontSizes: RAW_FEATURES.typography.fontSizes, - custom: { - 'line-height': RAW_FEATURES.custom[ 'line-height' ], - }, + customLineHeight: RAW_FEATURES.custom[ 'line-height' ], }, }, __experimentalGlobalStylesBaseStyles: PARSED_GLOBAL_STYLES, diff --git a/packages/components/src/mobile/global-styles-context/utils.native.js b/packages/components/src/mobile/global-styles-context/utils.native.js index 5838d3aa7d555a..97aee36a6f36b1 100644 --- a/packages/components/src/mobile/global-styles-context/utils.native.js +++ b/packages/components/src/mobile/global-styles-context/utils.native.js @@ -206,9 +206,7 @@ export function getGlobalStyles( rawStyles, rawFeatures ) { }, typography: { fontSizes: features?.typography?.fontSizes, - custom: { - 'line-height': features?.custom?.[ 'line-height' ], - }, + customLineHeight: features?.custom?.[ 'line-height' ], }, }, __experimentalGlobalStylesBaseStyles: globalStyles, diff --git a/packages/components/src/unit-control/index.native.js b/packages/components/src/unit-control/index.native.js index 9be10e31845b5e..b45f1c84e06792 100644 --- a/packages/components/src/unit-control/index.native.js +++ b/packages/components/src/unit-control/index.native.js @@ -94,6 +94,7 @@ function UnitControl( { accessibilityHint, unitButtonTextStyle, unit, + units, ] ); const getAnchor = useCallback( @@ -115,6 +116,9 @@ function UnitControl( { }; const renderUnitPicker = useCallback( () => { + if ( units === false ) { + return null; + } return ( { renderUnitButton } From fd0a2aad79785cabeac3b6542859ae356556d164 Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 24 Aug 2021 11:45:18 +1000 Subject: [PATCH 018/214] Bump mobile version in experiments page for gallery (#34220) This bumps the mobile app version in the note about backward compatibility in the experiments page for the gallery with image blocks flag. --- lib/experiments-page.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 224f058edaddfe..ef9f17dea1057c 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -58,7 +58,7 @@ function gutenberg_initialize_experiments_settings() { 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Test a new gallery block that uses nested image blocks (Warning: The new gallery is not compatible with WordPress mobile apps prior to version 18.1. If you use the mobile app, please update to the latest version to avoid content loss.)', 'gutenberg' ), + 'label' => __( 'Test a new gallery block that uses nested image blocks (Warning: The new gallery is not compatible with WordPress mobile apps prior to version 18.2. If you use the mobile app, please update to the latest version to avoid content loss.)', 'gutenberg' ), 'id' => 'gutenberg-gallery-refactor', ) ); From 2b437fef3dd67863fdaf7af0e59b08e9fc678c15 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 24 Aug 2021 12:44:50 +1000 Subject: [PATCH 019/214] Allow zero values for theme.json styles (#34251) --- lib/class-wp-theme-json-gutenberg.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 1b96923323b46b..cf713529b4f70d 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -548,8 +548,9 @@ private static function compute_style_properties( $styles ) { foreach ( self::PROPERTIES_METADATA as $css_property => $value_path ) { $value = self::get_property_value( $styles, $value_path ); - // Skip if empty or value represents array of longhand values. - if ( empty( $value ) || is_array( $value ) ) { + // Skip if empty and not "0" or value represents array of longhand values. + $has_missing_value = empty( $value ) && ! is_numeric( $value ); + if ( $has_missing_value || is_array( $value ) ) { continue; } From 1971a3c205b56879683c2a9e426c4c3cdba805ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Tue, 24 Aug 2021 10:10:08 +0200 Subject: [PATCH 020/214] Block Editor: Absorb parent block toolbar controls (#33955) * Block Editor: Try to absorb parent block toolbar controls * Try a new block controls group - parent * Expose conditionally general duotone and align block controls * Use __experimentalExposeControlsToChildren in block supports --- .../src/components/block-controls/fill.js | 8 ++-- .../src/components/block-controls/groups.js | 2 + .../src/components/block-controls/hook.js | 44 +++++++++++++++++++ .../src/components/block-toolbar/index.js | 4 ++ .../src/components/inner-blocks/index.js | 40 ++++++++++++----- .../use-display-block-controls/index.js | 17 +++---- packages/block-editor/src/hooks/align.js | 26 ++++++----- packages/block-editor/src/hooks/duotone.js | 2 +- packages/block-library/src/buttons/block.json | 3 +- packages/block-library/src/buttons/edit.js | 2 +- .../block-library/src/social-links/block.json | 3 +- .../block-library/src/social-links/edit.js | 2 +- 12 files changed, 110 insertions(+), 43 deletions(-) create mode 100644 packages/block-editor/src/components/block-controls/hook.js diff --git a/packages/block-editor/src/components/block-controls/fill.js b/packages/block-editor/src/components/block-controls/fill.js index 2392c3d07ee3ae..10b94728306863 100644 --- a/packages/block-editor/src/components/block-controls/fill.js +++ b/packages/block-editor/src/components/block-controls/fill.js @@ -15,18 +15,18 @@ import { /** * Internal dependencies */ -import useDisplayBlockControls from '../use-display-block-controls'; -import groups from './groups'; +import useBlockControlsFill from './hook'; export default function BlockControlsFill( { group = 'default', controls, children, + __experimentalExposeToChildren = false, } ) { - if ( ! useDisplayBlockControls() ) { + const Fill = useBlockControlsFill( group, __experimentalExposeToChildren ); + if ( ! Fill ) { return null; } - const Fill = groups[ group ].Fill; return ( diff --git a/packages/block-editor/src/components/block-controls/groups.js b/packages/block-editor/src/components/block-controls/groups.js index 42a94a4ab7a81b..9b9dfec8d8d450 100644 --- a/packages/block-editor/src/components/block-controls/groups.js +++ b/packages/block-editor/src/components/block-controls/groups.js @@ -7,12 +7,14 @@ const BlockControlsDefault = createSlotFill( 'BlockControls' ); const BlockControlsBlock = createSlotFill( 'BlockControlsBlock' ); const BlockControlsInline = createSlotFill( 'BlockFormatControls' ); const BlockControlsOther = createSlotFill( 'BlockControlsOther' ); +const BlockControlsParent = createSlotFill( 'BlockControlsParent' ); const groups = { default: BlockControlsDefault, block: BlockControlsBlock, inline: BlockControlsInline, other: BlockControlsOther, + parent: BlockControlsParent, }; export default groups; diff --git a/packages/block-editor/src/components/block-controls/hook.js b/packages/block-editor/src/components/block-controls/hook.js new file mode 100644 index 00000000000000..d907a9aad5c364 --- /dev/null +++ b/packages/block-editor/src/components/block-controls/hook.js @@ -0,0 +1,44 @@ +/** + * WordPress dependencies + */ +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import groups from './groups'; +import { store as blockEditorStore } from '../../store'; +import { useBlockEditContext } from '../block-edit/context'; +import useDisplayBlockControls from '../use-display-block-controls'; + +export default function useBlockControlsFill( group, exposeToChildren ) { + const isDisplayed = useDisplayBlockControls(); + const { clientId } = useBlockEditContext(); + const isParentDisplayed = useSelect( + ( select ) => { + const { getBlockName, hasSelectedInnerBlock } = select( + blockEditorStore + ); + const { hasBlockSupport } = select( blocksStore ); + return ( + exposeToChildren && + hasBlockSupport( + getBlockName( clientId ), + '__experimentalExposeControlsToChildren', + false + ) && + hasSelectedInnerBlock( clientId ) + ); + }, + [ exposeToChildren, clientId ] + ); + + if ( isDisplayed ) { + return groups[ group ]?.Fill; + } + if ( isParentDisplayed ) { + return groups.parent.Fill; + } + return null; +} diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index 599c936eeeb73a..def5b425760d10 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -123,6 +123,10 @@ export default function BlockToolbar( { hideDragHandle } ) {
{ shouldShowVisualToolbar && ( <> + { export function useInnerBlocksProps( props = {}, options = {} ) { const { clientId } = useBlockEditContext(); const isSmallScreen = useViewportMatch( 'medium', '<' ); - const hasOverlay = useSelect( + const { __experimentalCaptureToolbars, hasOverlay } = useSelect( ( select ) => { if ( ! clientId ) { - return; + return {}; } const { @@ -149,13 +153,22 @@ export function useInnerBlocksProps( props = {}, options = {} ) { hasSelectedInnerBlock, isNavigationMode, } = select( blockEditorStore ); + const blockName = getBlockName( clientId ); const enableClickThrough = isNavigationMode() || isSmallScreen; - return ( - getBlockName( clientId ) !== 'core/template' && - ! isBlockSelected( clientId ) && - ! hasSelectedInnerBlock( clientId, true ) && - enableClickThrough - ); + return { + __experimentalCaptureToolbars: select( + blocksStore + ).hasBlockSupport( + blockName, + '__experimentalExposeControlsToChildren', + false + ), + hasOverlay: + blockName !== 'core/template' && + ! isBlockSelected( clientId ) && + ! hasSelectedInnerBlock( clientId, true ) && + enableClickThrough, + }; }, [ clientId, isSmallScreen ] ); @@ -167,11 +180,14 @@ export function useInnerBlocksProps( props = {}, options = {} ) { } ), ] ); + const innerBlocksProps = { + __experimentalCaptureToolbars, + ...options, + }; const InnerBlocks = - options.value && options.onChange + innerBlocksProps.value && innerBlocksProps.onChange ? ControlledInnerBlocks : UncontrolledInnerBlocks; - return { ...props, ref, @@ -183,7 +199,7 @@ export function useInnerBlocksProps( props = {}, options = {} ) { } ), children: clientId ? ( - + ) : ( ), diff --git a/packages/block-editor/src/components/use-display-block-controls/index.js b/packages/block-editor/src/components/use-display-block-controls/index.js index a3f4e7c4362f6c..605556f295b968 100644 --- a/packages/block-editor/src/components/use-display-block-controls/index.js +++ b/packages/block-editor/src/components/use-display-block-controls/index.js @@ -11,11 +11,10 @@ import { store as blockEditorStore } from '../../store'; export default function useDisplayBlockControls() { const { isSelected, clientId, name } = useBlockEditContext(); - const isFirstAndSameTypeMultiSelected = useSelect( + return useSelect( ( select ) => { - // Don't bother checking, see OR statement below. if ( isSelected ) { - return; + return true; } const { @@ -24,16 +23,14 @@ export default function useDisplayBlockControls() { getMultiSelectedBlockClientIds, } = select( blockEditorStore ); - if ( ! isFirstMultiSelectedBlock( clientId ) ) { - return false; + if ( isFirstMultiSelectedBlock( clientId ) ) { + return getMultiSelectedBlockClientIds().every( + ( id ) => getBlockName( id ) === name + ); } - return getMultiSelectedBlockClientIds().every( - ( id ) => getBlockName( id ) === name - ); + return false; }, [ clientId, isSelected, name ] ); - - return isSelected || isFirstAndSameTypeMultiSelected; } diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js index 7cfd5f8b13fdef..b1e68b68bfe260 100644 --- a/packages/block-editor/src/hooks/align.js +++ b/packages/block-editor/src/hooks/align.js @@ -140,18 +140,20 @@ export const withToolbarControls = createHigherOrderComponent( props.setAttributes( { align: nextAlign } ); }; - return [ - validAlignments.length > 0 && props.isSelected && ( - - - - ), - , - ]; + return ( + <> + { validAlignments.length > 0 && ( + + + + ) } + + + ); }, 'withToolbarControls' ); diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index bf62161e1d1825..0bde4cdbe2c60e 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -140,7 +140,7 @@ function DuotonePanel( { attributes, setAttributes } ) { } return ( - + - + - + Date: Tue, 24 Aug 2021 15:53:18 +0200 Subject: [PATCH 021/214] Fix submenu positioning. (#34168) --- packages/block-library/src/navigation/style.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index bdc8183a235ea3..f63dab54701005 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -80,10 +80,13 @@ // Submenu indicator. .wp-block-page-list__submenu-icon, .wp-block-navigation-link__submenu-icon { + align-self: center; height: inherit; + line-height: 0; margin-left: 6px; svg { + display: inline-block; stroke: currentColor; } } From 641060d92e2723d82144ae1206828db7e19d3cf4 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Tue, 24 Aug 2021 15:53:49 +0200 Subject: [PATCH 022/214] Enable flex on nav container to fix space between. (#34258) --- packages/block-library/src/navigation/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index f63dab54701005..b37e4dbd0244f4 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -341,6 +341,7 @@ // Horizontal layout display: flex; flex-wrap: wrap; + flex: 1; } // Vertical layout From 790dd85a5d6e217817271452539f0c07db90c54f Mon Sep 17 00:00:00 2001 From: Jason Johnston Date: Tue, 24 Aug 2021 12:10:54 -0400 Subject: [PATCH 023/214] Revert "[RNMobile] Use Flexbox for Inserter Menu tabs on mobile (#34126)" (#34267) This reverts commit c238f4324354a4a87293fe83ff1268ce7889e1c2. Co-authored-by: jhnstn --- .../block-editor/src/components/inserter/menu.native.js | 5 +---- .../src/components/inserter/style.native.scss | 7 ++++--- .../block-editor/src/components/inserter/tabs.native.js | 8 +++++++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js index 5f556714456129..198bb761329e38 100644 --- a/packages/block-editor/src/components/inserter/menu.native.js +++ b/packages/block-editor/src/components/inserter/menu.native.js @@ -204,10 +204,7 @@ function InserterMenu( { > { ( { listProps } ) => ( - + { ! showTabs || filterValue ? ( { tabs.map( ( { component: TabComponent }, index ) => ( - + Date: Tue, 24 Aug 2021 12:18:35 -0400 Subject: [PATCH 024/214] Query Loop: Update Post Template sub-block icon (#34204) Use the "layout" icon instead of "loop." --- packages/block-library/src/post-template/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/post-template/index.js b/packages/block-library/src/post-template/index.js index 5e3bba213228bf..ace8add51d3289 100644 --- a/packages/block-library/src/post-template/index.js +++ b/packages/block-library/src/post-template/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { loop } from '@wordpress/icons'; +import { layout } from '@wordpress/icons'; /** * Internal dependencies @@ -14,7 +14,7 @@ const { name } = metadata; export { metadata, name }; export const settings = { - icon: loop, + icon: layout, edit, save, }; From 73f2b974b6b725fb5a1c20d32da04c8d6722d5b3 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Tue, 24 Aug 2021 11:28:50 -0500 Subject: [PATCH 025/214] Re-enable Android e2e tests (#34243) * Re-enable Android e2e tests * Pin Android emulator build ID A breakage within Android Studio emulators caused the Android e2e tests to fail with the following errors. To circumvent the issue, we leverage the ability to pin the emulator to a specific build ID. - https://git.io/JE3jX - https://issuetracker.google.com/issues/191805460 - https://issuetracker.google.com/issues/191799887 ``` dyld: lazy symbol binding failed: Symbol not found: _preadv ``` ``` adb: no devices/emulators found ``` --- .github/workflows/rnmobile-android-runner.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index 18eda2ad1ef5a6..8bb83dfd7b6ec0 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -15,9 +15,7 @@ concurrency: jobs: test: runs-on: macos-latest - # The false value below disables the test while we investigate a - # foundational error causing failures - if: ${{ false && (github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request') }} + if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} strategy: matrix: native-test-name: [gutenberg-editor-initial-html] @@ -41,9 +39,10 @@ jobs: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} - - uses: reactivecircus/android-emulator-runner@d2799957d660add41c61a5103e2fbb9e2889eb73 # v2.15.0 + - uses: reactivecircus/android-emulator-runner@5de26e4bd23bf523e8a4b7f077df8bfb8e52b50e # v2.19.1 with: api-level: 28 + emulator-build: 7425822 # https://git.io/JE3jX profile: pixel_xl script: npm run native test:e2e:android:local ${{ matrix.native-test-name }} From 45675ddfb6403e3979386894340d762756c5f2fd Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Tue, 24 Aug 2021 12:31:28 -0400 Subject: [PATCH 026/214] RNMobile: Fix broken documentation link (#34187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * RNMobile: Fix broken link in https://developer.wordpress.org/block-editor/ which was fixed by running npm run docs:build * Fix badly formatted docs table-of-content JSON * Fix typos in docs * Fix broken paths in docs Co-authored-by: Greg Ziółkowski --- docs/contributors/code/testing-overview.md | 4 ++-- docs/manifest.json | 6 ++++++ docs/toc.json | 15 ++++++++++----- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/contributors/code/testing-overview.md b/docs/contributors/code/testing-overview.md index b4b9e062701813..98827115b62580 100644 --- a/docs/contributors/code/testing-overview.md +++ b/docs/contributors/code/testing-overview.md @@ -380,11 +380,11 @@ To locally run the tests in debug mode, follow these steps: ### Native mobile end-to-end tests -Contributors to Gutenberg will note that PRs include continuous integration E2E tests running the native mobile E2E tests on Android and iOS. For troubleshooting failed tests, check our guide on [native mobile tests in continious integration](docs/contributors/native-mobile.md#native-mobile-e2e-tests-in-continuous-integration). More information on running these tests locally can be found in the [relevant directory README.md](https://github.com/WordPress/gutenberg/tree/HEAD/packages/react-native-editor/__device-tests__). +Contributors to Gutenberg will note that PRs include continuous integration E2E tests running the native mobile E2E tests on Android and iOS. For troubleshooting failed tests, check our guide on [native mobile tests in continuous integration](/docs/contributors/code/native-mobile.md#native-mobile-e2e-tests-in-continuous-integration). More information on running these tests locally can be found in [here](/packages/react-native-editor/__device-tests__/README.md). ### Native mobile integration tests -There is an ongoing effort to add integration tests to the native mobile project using the [`react-native-testing-library`](https://testing-library.com/docs/react-native-testing-library/intro/) library. A guide to writing integration tests can be found [here](native-mobile-integration-test-guide.md). +There is an ongoing effort to add integration tests to the native mobile project using the [`react-native-testing-library`](https://testing-library.com/docs/react-native-testing-library/intro/) library. A guide to writing integration tests can be found [here](/docs/contributors/code/native-mobile-integration-test-guide.md). ## End-to-end Testing diff --git a/docs/manifest.json b/docs/manifest.json index 3fa9280108d4fb..5fdf1d29c05a82 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -2015,6 +2015,12 @@ "markdown_source": "../docs/contributors/code/native-mobile.md", "parent": "code" }, + { + "title": "React Native Integration Test Guide", + "slug": "native-mobile-integration-test-guide", + "markdown_source": "../docs/contributors/code/native-mobile-integration-test-guide.md", + "parent": "code" + }, { "title": "Getting Started for the React Native based Mobile Gutenberg", "slug": "getting-started-native-mobile", diff --git a/docs/toc.json b/docs/toc.json index c8ce6140ef94f9..9d86afa1eb7f0e 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -178,11 +178,13 @@ }, { "docs/how-to-guides/accessibility.md": [] }, { "docs/how-to-guides/internationalization.md": [] }, - { "docs/how-to-guides/widgets/README.md": [ - { "docs/how-to-guides/widgets/overview.md": [] }, - { "docs/how-to-guides/widgets/opting-out.md": [] }, - { "docs/how-to-guides/widgets/legacy-widget-block.md": [] } - ] } + { + "docs/how-to-guides/widgets/README.md": [ + { "docs/how-to-guides/widgets/overview.md": [] }, + { "docs/how-to-guides/widgets/opting-out.md": [] }, + { "docs/how-to-guides/widgets/legacy-widget-block.md": [] } + ] + } ] }, { @@ -318,6 +320,9 @@ { "docs/contributors/code/managing-packages.md": [] }, { "docs/contributors/code/release.md": [] }, { "docs/contributors/code/native-mobile.md": [] }, + { + "docs/contributors/code/native-mobile-integration-test-guide.md": [] + }, { "docs/contributors/code/getting-started-native-mobile.md": [] } From 3437ce32c775205c0e73b786751d92ec9549e7cd Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Tue, 24 Aug 2021 14:28:33 -0500 Subject: [PATCH 027/214] Fix FlatList warning log from Columns block (#34200) Replace unsupported use of `flex-wrap` with dynamic setting of `horizontal` prop. The `horizontal` prop must be `false` to have the `numOfColumns` property take effect. ``` WARN `flexWrap: 'wrap'` is not supported with the `VirtualizedList` components.Consider using `numColumns` with `FlatList` instead. ``` Related links: - https://bit.ly/2UxBz3c - https://git.io/J0prs - https://git.io/J0prl - https://git.io/J0pr8 - https://git.io/J0prV --- packages/block-editor/src/components/block-list/index.native.js | 2 -- .../block-editor/src/components/block-list/style.native.scss | 1 - packages/block-library/src/columns/edit.native.js | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 4ce520ed818751..697a9ffc19daa6 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -262,8 +262,6 @@ export class BlockList extends Component { { flex: isRootList ? 1 : 0 }, ! isRootList && styles.overflowVisible, ] } - horizontal={ horizontal } - numColumns={ 1 } extraData={ this.getExtraData() } scrollEnabled={ isRootList } contentContainerStyle={ [ diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index fe63d30b4d0b21..6ff09277e9ea54 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -6,7 +6,6 @@ .horizontalContentContainer { flex-direction: row; - flex-wrap: wrap; justify-content: flex-start; align-items: stretch; overflow: visible; diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 9bf1ceae7ac99b..a7e765b8344a4e 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -274,7 +274,7 @@ function ColumnsEditContainer( { orientation={ columnsInRow > 1 ? 'horizontal' : undefined } - horizontal={ true } + horizontal={ columnsInRow > 1 } allowedBlocks={ ALLOWED_BLOCKS } contentResizeMode="stretch" onAddBlock={ onAddBlock } From 58e6686267b018eb6a1daa5810097b22bda27942 Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Tue, 24 Aug 2021 15:06:53 -0700 Subject: [PATCH 028/214] RNmobile: Fix the cancel button on Columns Block (#34249) --- .../src/components/block-variation-picker/style.native.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/block-editor/src/components/block-variation-picker/style.native.scss b/packages/block-editor/src/components/block-variation-picker/style.native.scss index cfee074a24a982..c8bb66b7a714a0 100644 --- a/packages/block-editor/src/components/block-variation-picker/style.native.scss +++ b/packages/block-editor/src/components/block-variation-picker/style.native.scss @@ -17,6 +17,8 @@ .cancelButton { color: $blue-wordpress; font-size: 16px; + padding-left: $grid-unit-20; + padding-right: $grid-unit-20; } .cancelButtonDark { From cb37d52289db37896ef9ab214c4305208c5649f1 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 25 Aug 2021 09:53:24 +1000 Subject: [PATCH 029/214] File block: Update transform from image to use image filename if caption is empty (#34256) --- packages/block-library/src/file/transforms.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/file/transforms.js b/packages/block-library/src/file/transforms.js index 30e9808cb25251..242b60adfd7354 100644 --- a/packages/block-library/src/file/transforms.js +++ b/packages/block-library/src/file/transforms.js @@ -10,6 +10,7 @@ import { createBlobURL } from '@wordpress/blob'; import { createBlock } from '@wordpress/blocks'; import { select } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; +import { getPath } from '@wordpress/url'; const transforms = { from: [ @@ -70,9 +71,11 @@ const transforms = { type: 'block', blocks: [ 'core/image' ], transform: ( attributes ) => { + const filename = getPath( attributes.url )?.split( '/' ).pop(); + return createBlock( 'core/file', { href: attributes.url, - fileName: attributes.caption, + fileName: attributes.caption || filename, textLinkHref: attributes.url, id: attributes.id, anchor: attributes.anchor, From e20259892d1063d3165ea920ce154226a01994cc Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 25 Aug 2021 04:23:23 +0000 Subject: [PATCH 030/214] Bump plugin version to 11.4.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- readme.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 63d4d1c4eb0955..474610ed70ee80 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the new block editor in core. * Requires at least: 5.6 * Requires PHP: 5.6 - * Version: 11.3.0 + * Version: 11.4.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 629b31cb939cbf..77a1fa1c1c7115 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "11.3.0", + "version": "11.4.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6d3ff4a2f29773..5aa265595e143b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "11.3.0", + "version": "11.4.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", diff --git a/readme.txt b/readme.txt index f0d9aa71b2bbb2..744abf34c84e4b 100644 --- a/readme.txt +++ b/readme.txt @@ -55,4 +55,4 @@ View release page. +To read the changelog for Gutenberg 11.4.0-rc.1, please navigate to the release page. From e7765c21767d834fec14f4581e8f4246e4442c0c Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 25 Aug 2021 06:00:08 +0000 Subject: [PATCH 031/214] Update Changelog for 11.4.0-rc.1 --- changelog.txt | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/changelog.txt b/changelog.txt index a25a0c2a499fd6..745a6fe44db929 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,152 @@ == Changelog == += 11.4.0-rc.1 = + +### Enhancements +- Accessibility + - Cover Block: Allow alt text in Cover blocks. ([33226](https://github.com/WordPress/gutenberg/pull/33226)) + - Add `aria-describedby` to custom select control component to describe currently-selected font size. ([33941](https://github.com/WordPress/gutenberg/pull/33941)) +- Block Editor + - Block Lists: improve iframe block, pattern and template previews. ([28165](https://github.com/WordPress/gutenberg/pull/28165)) +- Block Library + - Query Loop: Update Post Template sub-block icon. ([34204](https://github.com/WordPress/gutenberg/pull/34204)) + - Convert Gallery block to use Image blocks. ([25940](https://github.com/WordPress/gutenberg/pull/25940)) + - Post Featured Image: add duotone block supports. ([34113](https://github.com/WordPress/gutenberg/pull/34113)) + - Post Featured Image: add contextual help text to the `scale`property. ([34158](https://github.com/WordPress/gutenberg/pull/34158)) + - File block: update transform from image to use image filename if caption is empty. ([34256](https://github.com/WordPress/gutenberg/pull/34256)) + - Post date Block: add font weight support to the block. ([34070](https://github.com/WordPress/gutenberg/pull/34070)) + - Post terms: add font weight support to the block. ([34142](https://github.com/WordPress/gutenberg/pull/34142)) + - Site Tagline: Add font weight support. ([33983](https://github.com/WordPress/gutenberg/pull/33983)) + - Button: update spacing support to use axial padding. ([33859](https://github.com/WordPress/gutenberg/pull/33859)) +- Components + - Add deprecated props adapter for ColorPicker. ([34014](https://github.com/WordPress/gutenberg/pull/34014)) + - Wrap SegmentedControl in a BaseControl with an added `help` property. ([34017](https://github.com/WordPress/gutenberg/pull/34017)) + - Combobox: update the current selection if the list of suggestions is filtered. ([33928](https://github.com/WordPress/gutenberg/pull/33928)) + - Post Title: Use rich text hook and updating tag to `h1` ([31569](https://github.com/WordPress/gutenberg/pull/31569)) +- Design Tools + - Add layout default value support for blocks. ([34194](https://github.com/WordPress/gutenberg/pull/34194)) + - Dimensions Panel: Add padding tool as default for blocks where this is common setting. ([34026](https://github.com/WordPress/gutenberg/pull/34026)) +- Navigation Screen + - Update navigation screen topbar. ([34166](https://github.com/WordPress/gutenberg/pull/34166)) +- Packages + - Updates the "settings" icon, which toggles the display of additional controls in an interface. ([34165](https://github.com/WordPress/gutenberg/pull/34165)) +- Post Editor + - Migrate post editor feature preferences to the interface package. ([34154](https://github.com/WordPress/gutenberg/pull/34154)) +- Widgets Editor + - Migrate customize widgets feature preferences to interface package. ([34135](https://github.com/WordPress/gutenberg/pull/34135)) + - Refactor editor 'feature' preferences to interface package. ([33774](https://github.com/WordPress/gutenberg/pull/33774)) + +### Bug Fixes +- Build Tooling + - Webpack: Fix watch on `.json` and `.php` files. ([34024](https://github.com/WordPress/gutenberg/pull/34024)) +- Components + - Fix RTL on `Flex` component. ([33729](https://github.com/WordPress/gutenberg/pull/33729)) + - NavigationSidebar: fix template content for content-navigation-item preview. ([34203](https://github.com/WordPress/gutenberg/pull/34203)) + - Remove deprecated import style for storybook/addon-docs. ([34095](https://github.com/WordPress/gutenberg/pull/34095)) + - ToolsPanel: Add tools panel item deregistration. ([34085](https://github.com/WordPress/gutenberg/pull/34085)) + - Post Title: Remove wrapper div & fix border style. ([34167](https://github.com/WordPress/gutenberg/pull/34167)) +- Core Data + - GetEntityRecords return items even if some included IDs don't exist. ([34034](https://github.com/WordPress/gutenberg/pull/34034)) +- Block API + - Spacing/Dimensions Supports: Separate spacing from dimensions for compatibility purposes. ([34059](https://github.com/WordPress/gutenberg/pull/34059)) +- Block Editor + - Font-size adjustment for tablet and mobile device previews. ([33342](https://github.com/WordPress/gutenberg/pull/33342)) + - Fix single block selection by holding `shift` key. ([34137](https://github.com/WordPress/gutenberg/pull/34137)) + - Fix unwanted additional spaces added around pasted text on Windows. ([33607](https://github.com/WordPress/gutenberg/pull/33607)) + - Inserter: prevent non-deterministic order of inserter items. ([34078](https://github.com/WordPress/gutenberg/pull/34078)) + - Try: Fix multiselect toolbar indent and reformat `BlockContextualToolbar()`. ([34038](https://github.com/WordPress/gutenberg/pull/34038)) ([34173](https://github.com/WordPress/gutenberg/pull/34173)) +- Block Library + - Latest Comments: use site locale in the editor. ([33944](https://github.com/WordPress/gutenberg/pull/33944)) + - Navigation: fix vertical layout on the frontend. ([34226](https://github.com/WordPress/gutenberg/pull/34226)) + - Navigation: add z-index value to responsive menu overlay. ([34228](https://github.com/WordPress/gutenberg/pull/34228)) + - Navigation: enable flex on nav container to fix space between. ([34258](https://github.com/WordPress/gutenberg/pull/34258)) + - Navigation: fix submenu icon positioning. ([34168](https://github.com/WordPress/gutenberg/pull/34168)) + - Navigation block: add missing `` closing tag. ([34077](https://github.com/WordPress/gutenberg/pull/34077)) + - Post Excerpt: remove interactive formatting. ([34083](https://github.com/WordPress/gutenberg/pull/34083)) + - RichText: fix Space key for button and summary elements. ([30244](https://github.com/WordPress/gutenberg/pull/30244)) + - Search Block: add space between generated border class names. ([34025](https://github.com/WordPress/gutenberg/pull/34025)) +- Design Tools + - Allow zero values for Theme JSON styles. ([34251](https://github.com/WordPress/gutenberg/pull/34251)) +- Global Styles + - Site editor: Fix for how CSS Custom Properties are generated. ([33932](https://github.com/WordPress/gutenberg/pull/33932)) +- Packages + - Rich Text: add check to `toTree()` in replacements before accessing its type. ([34020](https://github.com/WordPress/gutenberg/pull/34020)) +- Post Editor + - Fix selector params in `isPluginItemPinned()` selector. ([34155](https://github.com/WordPress/gutenberg/pull/34155)) + + +### Performance +- Data Layer + - Data: Add a batch function to the data module to batch actions. ([34046](https://github.com/WordPress/gutenberg/pull/34046)) + +### Experiments +- Block API + - Block Editor: Absorb parent block toolbar controls. ([33955](https://github.com/WordPress/gutenberg/pull/33955)) + - Block Editor: Use groups for InspectorControls. ([34069](https://github.com/WordPress/gutenberg/pull/34069)) +- Block Library + - Add generic classnames to children of Navigation. ([33918](https://github.com/WordPress/gutenberg/pull/33918)) +- Global Styles + - Add slashes back to the Theme JSON. ([33919](https://github.com/WordPress/gutenberg/pull/33919)) + - Add block spacing gap configuration to theme.json and add support for this CSS variable to the "flow/default" layout. ([33812](https://github.com/WordPress/gutenberg/pull/33812)) + + +### Documentation +- Packages + - Add documentation for mobile components directory. ([33872](https://github.com/WordPress/gutenberg/pull/33872)) +- Handbook + - Alphabetize glossary entries. ([34058](https://github.com/WordPress/gutenberg/pull/34058)) + - Correct minor typos in wp-plugin.md ([34185](https://github.com/WordPress/gutenberg/pull/34185)) + - Remove extraneous params from `block_type_metadata` hook. ([34151](https://github.com/WordPress/gutenberg/pull/34151)) + - Update incorrect Settings examples in "Global Settings & Styles". ([34084](https://github.com/WordPress/gutenberg/pull/34084)) + - Use block.json to add attributes in create block tutorial. ([33978](https://github.com/WordPress/gutenberg/pull/33978)) + - Fix typo in block gap documentation in theme-json.md. ([34231](https://github.com/WordPress/gutenberg/pull/34231)) + - Fix broken mobile testing documentation link in testing-overview.md . ([34187](https://github.com/WordPress/gutenberg/pull/34187)) + - Fix typo in legacy-widget-block.md. ([34103](https://github.com/WordPress/gutenberg/pull/34103)) + - Update spelling and `fontSize` examples in create-block-theme.md. ([34152](https://github.com/WordPress/gutenberg/pull/34152)) +- Library + - Bump mobile version in experiments page for gallery. ([34220](https://github.com/WordPress/gutenberg/pull/34220)) + +### Code Quality +- Block Editor + - Render head and body with single portal for block previews. ([34208](https://github.com/WordPress/gutenberg/pull/34208)) + - BlockList: refactor element context for style/svg appending. ([34183](https://github.com/WordPress/gutenberg/pull/34183)) + - BlockList: Use InnerBlocks internally. ([29895](https://github.com/WordPress/gutenberg/pull/29895)) +- Site Editor + - Remove extra dom element used for template part overlay. ([34012](https://github.com/WordPress/gutenberg/pull/34012)) +- Components + - Unit Control: add unit tests for `getValidParsedUnit` utility method. ([34029](https://github.com/WordPress/gutenberg/pull/34029)) + - Rename `SegmentedControl` to `ToggleGroupControl`. ([34111](https://github.com/WordPress/gutenberg/pull/34111)) + - Dropdown Menu: remove min-width from the dropdown component and add whitespace rule to avoid wrapping ([33995](https://github.com/WordPress/gutenberg/pull/33995)) +- Core Data + - Allow passing store definitions to controls. ([34170](https://github.com/WordPress/gutenberg/pull/34170)) + +### Tools +- ESLint + - Eslint plugin: Use @typescript-eslint/no-duplicate-imports in TS projects. ([34055](https://github.com/WordPress/gutenberg/pull/34055)) +- GitHub Contributor Templates + - Issue Forms: Simplify the bug report form template. ([34007](https://github.com/WordPress/gutenberg/pull/34007)) +- Logs + - Hide deprecation logs under a console group. ([34163](https://github.com/WordPress/gutenberg/pull/34163)) +- Testing + - Emulate reduced-motion in end-to-end tests. ([34132](https://github.com/WordPress/gutenberg/pull/34132)) + - Re-enable Android end-to-end tests. ([34243](https://github.com/WordPress/gutenberg/pull/34243)) + - Remove extra props from Cover deprecations. ([34066](https://github.com/WordPress/gutenberg/pull/34066)) + - Remove the `ENVIRONMENT_DIRECTORY` env variable that was added to the performance jobs. ([34086](https://github.com/WordPress/gutenberg/pull/34086)) + - Add snapshot test for changelog formatting. ([34049](https://github.com/WordPress/gutenberg/pull/34049)) + - Experiment with using REST API in end-to-end tests to build up states. ([33414](https://github.com/WordPress/gutenberg/pull/33414)) +- Build Tooling + - Automated Changelog: force group all documentation tasks under `Documentation`. ([34042](https://github.com/WordPress/gutenberg/pull/34042)) + - Automated Changelog: rename "Editor" grouping to "Post Editor" to avoid ambiguity with other editors. ([34093](https://github.com/WordPress/gutenberg/pull/34093)) + - Automated Changelog: sort feature groups by issue name. ([34071](https://github.com/WordPress/gutenberg/pull/34071)) + - Automated Changelog: use nested headings for feature groups instead of indenting lists. ([34040](https://github.com/WordPress/gutenberg/pull/34040)) + - Automated Changelog: remove `Uncategorized` header in output and place items at top. ([34037](https://github.com/WordPress/gutenberg/pull/34037)) + - Add Typescript extensions to watched files. ([34094](https://github.com/WordPress/gutenberg/pull/34094)) + - Remove obsolete step that pushes tags in npm publishing flow. ([34114](https://github.com/WordPress/gutenberg/pull/34114)) + + + + + = 11.3.0 = ### Enhancements From 4037448e9214063128960fc086ae4cc4744158e0 Mon Sep 17 00:00:00 2001 From: Matthew Reishus Date: Wed, 25 Aug 2021 01:05:27 -0500 Subject: [PATCH 032/214] Change default value of enableCustomFields to undefined (#33931) * Change default value of enableCustomFields to undefined * Add comment explaining true/false/undefined for enableCustomFields * Preferences: Hide 'Additional' section when custom fields are disabled and there are no meta boxes * Tweak comments Co-authored-by: Robert Anderson --- .../edit-post/src/components/preferences-modal/index.js | 6 ++---- .../preferences-modal/test/__snapshots__/index.js.snap | 6 ++---- packages/editor/src/store/defaults.js | 9 +++++++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/edit-post/src/components/preferences-modal/index.js b/packages/edit-post/src/components/preferences-modal/index.js index 3a536a74140bde..ad67783a7f10a2 100644 --- a/packages/edit-post/src/components/preferences-modal/index.js +++ b/packages/edit-post/src/components/preferences-modal/index.js @@ -243,14 +243,12 @@ export default function PreferencesModal() { /> -
- -
+ /> ), }, diff --git a/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap index 5e96f24c36fef3..d8351581e23684 100644 --- a/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap @@ -179,12 +179,10 @@ exports[`PreferencesModal should match snapshot when the modal is active small v /> -
- -
+ /> diff --git a/packages/editor/src/store/defaults.js b/packages/editor/src/store/defaults.js index 94125a58f1392f..03824561549f71 100644 --- a/packages/editor/src/store/defaults.js +++ b/packages/editor/src/store/defaults.js @@ -14,7 +14,12 @@ export const PREFERENCES_DEFAULTS = { * allowedBlockTypes boolean|Array Allowed block types * richEditingEnabled boolean Whether rich editing is enabled or not * codeEditingEnabled boolean Whether code editing is enabled or not - * enableCustomFields boolean Whether the WordPress custom fields are enabled or not + * enableCustomFields boolean Whether the WordPress custom fields are enabled or not. + * true = the user has opted to show the Custom Fields panel at the bottom of the editor. + * false = the user has opted to hide the Custom Fields panel at the bottom of the editor. + * undefined = the current environment does not support Custom Fields, + * so the option toggle in Preferences -> Panels to + * enable the Custom Fields panel is not displayed. * autosaveInterval number Autosave Interval * availableTemplates array? The available post templates * disablePostFormats boolean Whether or not the post formats are disabled @@ -27,6 +32,6 @@ export const EDITOR_SETTINGS_DEFAULTS = { richEditingEnabled: true, codeEditingEnabled: true, - enableCustomFields: false, + enableCustomFields: undefined, supportsLayout: true, }; From 9a83b74ccd8c596eb840c506c73bbd0cebbbe5cc Mon Sep 17 00:00:00 2001 From: chad1008 <13856531+chad1008@users.noreply.github.com> Date: Wed, 25 Aug 2021 04:11:33 -0400 Subject: [PATCH 033/214] Align labels on focal point picker position controls above the text inputs to allow space for longer text when translated (#34209) --- packages/components/src/focal-point-picker/controls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/focal-point-picker/controls.js b/packages/components/src/focal-point-picker/controls.js index ae375a1bc32ceb..36991e05604139 100644 --- a/packages/components/src/focal-point-picker/controls.js +++ b/packages/components/src/focal-point-picker/controls.js @@ -60,7 +60,7 @@ function UnitControl( props ) { return ( Date: Wed, 25 Aug 2021 19:13:55 +0900 Subject: [PATCH 034/214] General Interface: Make permalinks documentation URL translatable (#34282) * Make the URL translatable. * Update packages/edit-post/src/components/sidebar/post-link/index.js Co-authored-by: Hiroshi Urabe Co-authored-by: Hiroshi Urabe --- .../edit-post/src/components/sidebar/post-link/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/edit-post/src/components/sidebar/post-link/index.js b/packages/edit-post/src/components/sidebar/post-link/index.js index 5b6ac7f7e92206..a3905a2275e5ec 100644 --- a/packages/edit-post/src/components/sidebar/post-link/index.js +++ b/packages/edit-post/src/components/sidebar/post-link/index.js @@ -95,7 +95,11 @@ function PostLink( { />

{ __( 'The last part of the URL.' ) }{ ' ' } - + { __( 'Read about permalinks' ) }

From 636b8710eefc530e0f17770d32fc7b27eedbbc55 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 25 Aug 2021 11:14:17 +0100 Subject: [PATCH 035/214] More work on the stability of the performance metrics (#34229) --- .../specs/performance/post-editor.test.js | 188 ++++++++++-------- 1 file changed, 108 insertions(+), 80 deletions(-) diff --git a/packages/e2e-tests/specs/performance/post-editor.test.js b/packages/e2e-tests/specs/performance/post-editor.test.js index 8045291cb331a4..0bbfcd18660369 100644 --- a/packages/e2e-tests/specs/performance/post-editor.test.js +++ b/packages/e2e-tests/specs/performance/post-editor.test.js @@ -31,18 +31,18 @@ import { jest.setTimeout( 1000000 ); describe( 'Post Editor Performance', () => { - it( 'Loading, typing and selecting blocks', async () => { - const traceFile = __dirname + '/trace.json'; - let traceResults; - const results = { - load: [], - type: [], - focus: [], - inserterOpen: [], - inserterHover: [], - inserterSearch: [], - }; + const results = { + load: [], + type: [], + focus: [], + inserterOpen: [], + inserterHover: [], + inserterSearch: [], + }; + const traceFile = __dirname + '/trace.json'; + let traceResults; + beforeAll( async () => { const html = readFile( join( __dirname, '../../assets/large-post.html' ) ); @@ -63,7 +63,30 @@ describe( 'Post Editor Performance', () => { dispatch( 'core/block-editor' ).resetBlocks( blocks ); }, html ); await saveDraft(); + } ); + + afterAll( async () => { + const resultsFilename = basename( __filename, '.js' ) + '.results.json'; + writeFileSync( + join( __dirname, resultsFilename ), + JSON.stringify( results, null, 2 ) + ); + deleteFile( traceFile ); + } ); + beforeEach( async () => { + // Disable auto-save to avoid impacting the metrics. + await page.evaluate( () => { + window.wp.data + .dispatch( 'core/edit-post' ) + .__experimentalUpdateLocalAutosaveInterval( 100000000000 ); + window.wp.data + .dispatch( 'core/editor' ) + .updateEditorSettings( { autosaveInterval: 100000000000 } ); + } ); + } ); + + it( 'Loading', async () => { // Measuring loading time let i = 5; while ( i-- ) { @@ -72,7 +95,77 @@ describe( 'Post Editor Performance', () => { await page.waitForSelector( '.wp-block' ); results.load.push( new Date() - startTime ); } + } ); + + it( 'Typing', async () => { + // Measuring typing performance + await insertBlock( 'Paragraph' ); + let i = 20; + await page.tracing.start( { + path: traceFile, + screenshots: false, + categories: [ 'devtools.timeline' ], + } ); + while ( i-- ) { + // Wait for the browser to be idle before starting the monitoring. + // The timeout should be big enough to allow all async tasks tor run. + // And also to allow Rich Text to mark the change as persistent. + // eslint-disable-next-line no-restricted-syntax + await page.waitForTimeout( 2000 ); + await page.keyboard.type( 'x' ); + } + await page.tracing.stop(); + traceResults = JSON.parse( readFile( traceFile ) ); + const [ + keyDownEvents, + keyPressEvents, + keyUpEvents, + ] = getTypingEventDurations( traceResults ); + if ( + keyDownEvents.length === keyPressEvents.length && + keyPressEvents.length === keyUpEvents.length + ) { + // The first character typed triggers a longer time (isTyping change) + // It can impact the stability of the metric, so we exclude it. + for ( let j = 1; j < keyDownEvents.length; j++ ) { + results.type.push( + keyDownEvents[ j ] + keyPressEvents[ j ] + keyUpEvents[ j ] + ); + } + } + } ); + + it( 'Selecting blocks', async () => { + // Measuring block selection performance + await createNewPost(); + await page.evaluate( () => { + const { createBlock } = window.wp.blocks; + const { dispatch } = window.wp.data; + const blocks = window.lodash + .times( 1000 ) + .map( () => createBlock( 'core/paragraph' ) ); + dispatch( 'core/block-editor' ).resetBlocks( blocks ); + } ); + const paragraphs = await page.$$( '.wp-block' ); + await page.tracing.start( { + path: traceFile, + screenshots: false, + categories: [ 'devtools.timeline' ], + } ); + await paragraphs[ 0 ].click(); + for ( let j = 1; j <= 10; j++ ) { + // Wait for the browser to be idle before starting the monitoring. + // eslint-disable-next-line no-restricted-syntax + await page.waitForTimeout( 1000 ); + await paragraphs[ j ].click(); + } + await page.tracing.stop(); + traceResults = JSON.parse( readFile( traceFile ) ); + const [ focusEvents ] = getSelectionEventDurations( traceResults ); + results.focus = focusEvents; + } ); + it( 'Opening the inserter', async () => { // Measure time to open inserter await page.waitForSelector( '.edit-post-layout' ); for ( let j = 0; j < 10; j++ ) { @@ -90,7 +183,9 @@ describe( 'Post Editor Performance', () => { } await closeGlobalBlockInserter(); } + } ); + it( 'Searching the inserter', async () => { // Measure time to search the inserter and get results await openGlobalBlockInserter(); for ( let j = 0; j < 10; j++ ) { @@ -123,7 +218,9 @@ describe( 'Post Editor Performance', () => { await page.keyboard.press( 'Backspace' ); } await closeGlobalBlockInserter(); + } ); + it( 'Hovering Inserter Items', async () => { // Measure inserter hover performance const paragraphBlockItem = '.block-editor-inserter__menu .editor-block-list-item-paragraph'; @@ -157,74 +254,5 @@ describe( 'Post Editor Performance', () => { } } await closeGlobalBlockInserter(); - - // Measuring typing performance - await insertBlock( 'Paragraph' ); - i = 20; - await page.tracing.start( { - path: traceFile, - screenshots: false, - categories: [ 'devtools.timeline' ], - } ); - while ( i-- ) { - // Wait for the browser to be idle before starting the monitoring. - // eslint-disable-next-line no-restricted-syntax - await page.waitForTimeout( 200 ); - await page.keyboard.type( 'x' ); - } - await page.tracing.stop(); - traceResults = JSON.parse( readFile( traceFile ) ); - const [ - keyDownEvents, - keyPressEvents, - keyUpEvents, - ] = getTypingEventDurations( traceResults ); - if ( - keyDownEvents.length === keyPressEvents.length && - keyPressEvents.length === keyUpEvents.length - ) { - for ( let j = 0; j < keyDownEvents.length; j++ ) { - results.type.push( - keyDownEvents[ j ] + keyPressEvents[ j ] + keyUpEvents[ j ] - ); - } - } - - // Measuring block selection performance - await createNewPost(); - await page.evaluate( () => { - const { createBlock } = window.wp.blocks; - const { dispatch } = window.wp.data; - const blocks = window.lodash - .times( 1000 ) - .map( () => createBlock( 'core/paragraph' ) ); - dispatch( 'core/block-editor' ).resetBlocks( blocks ); - } ); - const paragraphs = await page.$$( '.wp-block' ); - await page.tracing.start( { - path: traceFile, - screenshots: false, - categories: [ 'devtools.timeline' ], - } ); - await paragraphs[ 0 ].click(); - for ( let j = 1; j <= 10; j++ ) { - // Wait for the browser to be idle before starting the monitoring. - // eslint-disable-next-line no-restricted-syntax - await page.waitForTimeout( 200 ); - await paragraphs[ j ].click(); - } - await page.tracing.stop(); - traceResults = JSON.parse( readFile( traceFile ) ); - const [ focusEvents ] = getSelectionEventDurations( traceResults ); - results.focus = focusEvents; - - const resultsFilename = basename( __filename, '.js' ) + '.results.json'; - writeFileSync( - join( __dirname, resultsFilename ), - JSON.stringify( results, null, 2 ) - ); - deleteFile( traceFile ); - - expect( true ).toBe( true ); } ); } ); From 8a88997867b09a57e536a34ce6ffe8a07e1f3f62 Mon Sep 17 00:00:00 2001 From: Vicente Canales <1157901+vcanales@users.noreply.github.com> Date: Wed, 25 Aug 2021 06:14:47 -0400 Subject: [PATCH 036/214] release workflow: only commit modified changelogs (#34211) --- .github/workflows/upload-release-to-plugin-repo.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/upload-release-to-plugin-repo.yml b/.github/workflows/upload-release-to-plugin-repo.yml index d7561fe3b296f4..59cfed85c1b8f5 100644 --- a/.github/workflows/upload-release-to-plugin-repo.yml +++ b/.github/workflows/upload-release-to-plugin-repo.yml @@ -79,8 +79,14 @@ jobs: - name: Commit the Changelog update run: | git add changelog.txt - git commit -m "Update Changelog for ${TAG#v}" - git push --set-upstream origin "${{ matrix.branch }}" + # Remove files that are not meant to be commited + # ie. release_notes.txt created on the previous step. + git clean -fd + # Only attempt to commit changelog if it has been modified. + if ! git diff-index --quiet HEAD --; then + git commit -m "Update Changelog for ${TAG#v}" + git push --set-upstream origin "${{ matrix.branch }}" + fi - name: Upload Changelog artifact uses: actions/upload-artifact@e448a9b857ee2131e752b06002bf0e093c65e571 # v2.2.2 From ac41a59a2ce33cce69c4a6374a6dc759888d0ce1 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Wed, 25 Aug 2021 11:52:48 +0100 Subject: [PATCH 037/214] useDropZone: Ensure drag event targets HTMLElement (#34272) Attempts to fix a TypeError that has been logged in the wild but is yet to be reproduced. In production builds, it manifests as `e.dataset is undefined`, pointing to the useDropZone hook. * Don't cast FocusTarget instance to HTMLElement prior to calling isElementInZone. * Perform type validations inside isElementInZone, then cast to HTMLElement. --- .../compose/src/hooks/use-drop-zone/index.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/compose/src/hooks/use-drop-zone/index.js b/packages/compose/src/hooks/use-drop-zone/index.js index c2a6a4224f55fb..ea58eab14b9cb5 100644 --- a/packages/compose/src/hooks/use-drop-zone/index.js +++ b/packages/compose/src/hooks/use-drop-zone/index.js @@ -73,18 +73,24 @@ export default function useDropZone( { /** * Checks if an element is in the drop zone. * - * @param {HTMLElement|null} elementToCheck + * @param {EventTarget|null} targetToCheck * * @return {boolean} True if in drop zone, false if not. */ - function isElementInZone( elementToCheck ) { + function isElementInZone( targetToCheck ) { + const { defaultView } = ownerDocument; if ( - ! elementToCheck || - ! element.contains( elementToCheck ) + ! targetToCheck || + ! defaultView || + ! ( targetToCheck instanceof defaultView.HTMLElement ) || + ! element.contains( targetToCheck ) ) { return false; } + /** @type {HTMLElement|null} */ + let elementToCheck = targetToCheck; + do { if ( elementToCheck.dataset.isDropZone ) { return elementToCheck === element; @@ -155,11 +161,7 @@ export default function useDropZone( { // leaving the drop zone, which means the `relatedTarget` // (element that has been entered) should be outside the drop // zone. - if ( - isElementInZone( - /** @type {HTMLElement|null} */ ( event.relatedTarget ) - ) - ) { + if ( isElementInZone( event.relatedTarget ) ) { return; } From 599b60d2cc8b26b9bc2141305cfe5e07b23dab88 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 25 Aug 2021 21:00:24 +1000 Subject: [PATCH 038/214] Default batch processor: Respect the batch endpoint's maxItems (#34280) This updates the default batch processor to make multiple batch requests if the number of requests to process exceeds the number of requests that the batch endpoint can handle. We determine the number of requests that the batch endpoint can handle by making a preflight OPTIONS request to /batch/v1. By default it is 25 requests. See https://make.wordpress.org/core/2020/11/20/rest-api-batch-framework-in-wordpress-5-6/. --- .../core-data/src/batch/default-processor.js | 83 +++++++++++++------ .../src/batch/test/default-processor.js | 79 ++++++++++++------ 2 files changed, 110 insertions(+), 52 deletions(-) diff --git a/packages/core-data/src/batch/default-processor.js b/packages/core-data/src/batch/default-processor.js index d459923218e129..34f38ff5c3d0da 100644 --- a/packages/core-data/src/batch/default-processor.js +++ b/packages/core-data/src/batch/default-processor.js @@ -1,10 +1,23 @@ +/** + * External dependencies + */ +import { chunk } from 'lodash'; + /** * WordPress dependencies */ import apiFetch from '@wordpress/api-fetch'; /** - * Default batch processor. Sends its input requests to /v1/batch. + * Maximum number of requests to place in a single batch request. Obtained by + * sending a preflight OPTIONS request to /batch/v1/. + * + * @type {number?} + */ +let maxItems = null; + +/** + * Default batch processor. Sends its input requests to /batch/v1. * * @param {Array} requests List of API requests to perform at once. * @@ -13,33 +26,51 @@ import apiFetch from '@wordpress/api-fetch'; * (if not ). */ export default async function defaultProcessor( requests ) { - const batchResponse = await apiFetch( { - path: '/batch/v1', - method: 'POST', - data: { - validation: 'require-all-validate', - requests: requests.map( ( request ) => ( { - path: request.path, - body: request.data, // Rename 'data' to 'body'. - method: request.method, - headers: request.headers, - } ) ), - }, - } ); - - if ( batchResponse.failed ) { - return batchResponse.responses.map( ( response ) => ( { - error: response?.body, - } ) ); + if ( maxItems === null ) { + const preflightResponse = await apiFetch( { + path: '/batch/v1', + method: 'OPTIONS', + } ); + maxItems = preflightResponse.endpoints[ 0 ].args.requests.maxItems; } - return batchResponse.responses.map( ( response ) => { - const result = {}; - if ( response.status >= 200 && response.status < 300 ) { - result.output = response.body; + const results = []; + + for ( const batchRequests of chunk( requests, maxItems ) ) { + const batchResponse = await apiFetch( { + path: '/batch/v1', + method: 'POST', + data: { + validation: 'require-all-validate', + requests: batchRequests.map( ( request ) => ( { + path: request.path, + body: request.data, // Rename 'data' to 'body'. + method: request.method, + headers: request.headers, + } ) ), + }, + } ); + + let batchResults; + + if ( batchResponse.failed ) { + batchResults = batchResponse.responses.map( ( response ) => ( { + error: response?.body, + } ) ); } else { - result.error = response.body; + batchResults = batchResponse.responses.map( ( response ) => { + const result = {}; + if ( response.status >= 200 && response.status < 300 ) { + result.output = response.body; + } else { + result.error = response.body; + } + return result; + } ); } - return result; - } ); + + results.push( ...batchResults ); + } + + return results; } diff --git a/packages/core-data/src/batch/test/default-processor.js b/packages/core-data/src/batch/test/default-processor.js index c6d2515410b826..c712ecdff21aa5 100644 --- a/packages/core-data/src/batch/test/default-processor.js +++ b/packages/core-data/src/batch/test/default-processor.js @@ -11,6 +11,18 @@ import defaultProcessor from '../default-processor'; jest.mock( '@wordpress/api-fetch' ); describe( 'defaultProcessor', () => { + const preflightResponse = { + endpoints: [ + { + args: { + requests: { + maxItems: 25, + }, + }, + }, + ], + }; + const requests = [ { path: '/v1/cricketers', @@ -26,7 +38,12 @@ describe( 'defaultProcessor', () => { }, ]; - const expectedFetchOptions = { + const expectedPreflightOptions = { + path: '/batch/v1', + method: 'OPTIONS', + }; + + const expectedBatchOptions = { path: '/batch/v1', method: 'POST', data: { @@ -49,21 +66,26 @@ describe( 'defaultProcessor', () => { }; it( 'handles a successful request', async () => { - apiFetch.mockImplementation( async () => ( { - failed: false, - responses: [ - { - status: 200, - body: 'Lyon', - }, - { - status: 400, - body: 'Error!', - }, - ], - } ) ); + apiFetch.mockImplementation( async ( { method } ) => + method === 'OPTIONS' + ? preflightResponse + : { + failed: false, + responses: [ + { + status: 200, + body: 'Lyon', + }, + { + status: 400, + body: 'Error!', + }, + ], + } + ); const results = await defaultProcessor( requests ); - expect( apiFetch ).toHaveBeenCalledWith( expectedFetchOptions ); + expect( apiFetch ).toHaveBeenCalledWith( expectedPreflightOptions ); + expect( apiFetch ).toHaveBeenCalledWith( expectedBatchOptions ); expect( results ).toEqual( [ { output: 'Lyon' }, { error: 'Error!' }, @@ -71,18 +93,23 @@ describe( 'defaultProcessor', () => { } ); it( 'handles a failed request', async () => { - apiFetch.mockImplementation( async () => ( { - failed: true, - responses: [ - null, - { - status: 400, - body: 'Error!', - }, - ], - } ) ); + apiFetch.mockImplementation( async ( { method } ) => + method === 'OPTIONS' + ? preflightResponse + : { + failed: true, + responses: [ + null, + { + status: 400, + body: 'Error!', + }, + ], + } + ); const results = await defaultProcessor( requests ); - expect( apiFetch ).toHaveBeenCalledWith( expectedFetchOptions ); + expect( apiFetch ).toHaveBeenCalledWith( expectedPreflightOptions ); + expect( apiFetch ).toHaveBeenCalledWith( expectedBatchOptions ); expect( results ).toEqual( [ { error: undefined }, { error: 'Error!' }, From 60d907bc33fcbd47235b8e141909b368966c7995 Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Wed, 25 Aug 2021 08:55:47 -0400 Subject: [PATCH 039/214] Block Toolbar & Popover component - Prevent sticky position from causing permanently obscured areas of the selected block. (#33981) * try using next block as anchor * place popover below block if available when sticky * if starts at bottom of block, keep there * fix edge cases * add bottom sticky position * fix jumpy bug on using toolbar * revert to default behavior as soon as there is room. no bottom sticky necessary * refactor logic and comment * remove unnecessary else * gross selectors * pass content wrapper through to popover as prop * minor refactor * use bottom sticky if necessary * update names, comments, JSDocs * add basic store tests * remove store goo * use the contentRef * Update packages/block-editor/src/components/block-tools/block-popover.js --- .../components/block-tools/block-popover.js | 3 + packages/components/src/popover/index.js | 4 +- packages/components/src/popover/utils.js | 99 ++++++++++++------- 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/block-popover.js b/packages/block-editor/src/components/block-tools/block-popover.js index 8371cfff9bbba8..59d9ae24a71d3a 100644 --- a/packages/block-editor/src/components/block-tools/block-popover.js +++ b/packages/block-editor/src/components/block-tools/block-popover.js @@ -213,6 +213,9 @@ function BlockPopover( { // Observe movement for block animations (especially horizontal). __unstableObserveElement={ node } shouldAnchorIncludePadding + // Used to safeguard sticky position behavior against cases where it would permanently + // obscure specific sections of a block. + __unstableEditorCanvasWrapper={ __unstableContentRef?.current } > { ( shouldShowContextualToolbar || isToolbarForced ) && (
Date: Wed, 25 Aug 2021 13:49:20 +0000 Subject: [PATCH 040/214] Editor package: Replace hardcoded store key (#34296) --- packages/editor/src/components/local-autosave-monitor/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/local-autosave-monitor/index.js b/packages/editor/src/components/local-autosave-monitor/index.js index 4587ec7611acef..b406a2545cf395 100644 --- a/packages/editor/src/components/local-autosave-monitor/index.js +++ b/packages/editor/src/components/local-autosave-monitor/index.js @@ -56,7 +56,7 @@ function useAutosaveNotice() { } ), [] ); - const { getEditedPostAttribute } = useSelect( 'core/editor' ); + const { getEditedPostAttribute } = useSelect( editorStore ); const { createWarningNotice, removeNotice } = useDispatch( noticesStore ); const { editPost, resetEditorBlocks } = useDispatch( editorStore ); From 3cc2b1730d16e06ac79b02526d77e6a74816999f Mon Sep 17 00:00:00 2001 From: Jason Johnston Date: Wed, 25 Aug 2021 12:45:53 -0400 Subject: [PATCH 041/214] [RNMobile] Full screen block inserter menu for Android (#34129) * Use flexbox for tab positioning * Allow bottomsheet to scroll while fullscreen Changes cherry picked from https://github.com/WordPress/gutenberg/pull/34018/commits/c5aa56cd31b6abbd96cf1bc76d13b54189b078d3 * Fix input height * Fix active search cut off * Use fullscreen on Android only * Add prop to allow drag indicator on Android * Pass in initial number of items to render * Update logic to show the drag indicator * Fix block inserter scroll view height (#34139) * Fix block inserter scroll view height Leverages flex layout properties rather than explicit height to ensure the scroll view coordinates its height with its sibling elements. * Rename Sass selectors for improved clarity Avoid referencing "tabs" as that is not relevant as this level of the UI. * Enable inserter block search * Fix revert issue * Blur inserter search when keyboard closes (#34278) Co-authored-by: jhnstn * Filter embed blocks from Inserter search block list Co-authored-by: jhnstn Co-authored-by: David Calhoun <438664+dcalhoun@users.noreply.github.com> --- .../block-types-list/index.native.js | 10 ++++++++-- .../src/components/inserter/menu.native.js | 14 +++++++++++--- .../inserter/search-results.native.js | 19 ++++++++++++++++++- .../src/components/inserter/style.native.scss | 13 ++++++++----- .../src/components/inserter/tabs.native.js | 8 +------- .../src/mobile/bottom-sheet/index.native.js | 12 +++++++++++- .../src/search-control/index.native.js | 17 ++++++++++++----- .../platform-style.android.scss | 5 +---- .../search-control/platform-style.ios.scss | 3 --- .../src/search-control/style.native.scss | 4 +--- 10 files changed, 71 insertions(+), 34 deletions(-) diff --git a/packages/block-editor/src/components/block-types-list/index.native.js b/packages/block-editor/src/components/block-types-list/index.native.js index 485cde15fbf1fa..81795d37e87789 100644 --- a/packages/block-editor/src/components/block-types-list/index.native.js +++ b/packages/block-editor/src/components/block-types-list/index.native.js @@ -22,7 +22,13 @@ import styles from './style.scss'; const MIN_COL_NUM = 3; -export default function BlockTypesList( { name, items, onSelect, listProps } ) { +export default function BlockTypesList( { + name, + items, + onSelect, + listProps, + initialNumToRender = 3, +} ) { const [ numberOfColumns, setNumberOfColumns ] = useState( MIN_COL_NUM ); const [ itemWidth, setItemWidth ] = useState(); const [ maxWidth, setMaxWidth ] = useState(); @@ -81,7 +87,7 @@ export default function BlockTypesList( { name, items, onSelect, listProps } ) { keyboardShouldPersistTaps="always" numColumns={ numberOfColumns } data={ items } - initialNumToRender={ 3 } + initialNumToRender={ initialNumToRender } ItemSeparatorComponent={ () => ( { ( { listProps } ) => ( - + { ! showTabs || filterValue ? ( ) : ( { const allItems = select( blockEditorStore ).getInserterItems( rootClientId ); - const filteredItems = searchItems( allItems, filterValue ); + + const blockItems = allItems.filter( + ( { id, category } ) => + ! NON_BLOCK_CATEGORIES.includes( category ) && + // We don't want to show all possible embed variations + // as different blocks in the inserter. We'll only show a + // few popular ones. + ( category !== 'embed' || + ( category === 'embed' && + ALLOWED_EMBED_VARIATIONS.includes( id ) ) ) + ); + + const filteredItems = searchItems( blockItems, filterValue ); return { blockTypes: filteredItems }; }, @@ -46,6 +62,7 @@ function InserterSearchResults( { return ( ); diff --git a/packages/block-editor/src/components/inserter/style.native.scss b/packages/block-editor/src/components/inserter/style.native.scss index 8fcb9c1cd04529..105af1f72b7d91 100644 --- a/packages/block-editor/src/components/inserter/style.native.scss +++ b/packages/block-editor/src/components/inserter/style.native.scss @@ -8,7 +8,11 @@ color: $blue-30; } -.list { +.inserter-menu__list-wrapper { + flex: 1; +} + +.inserter-menu__list { padding-bottom: 20; padding-top: 8; } @@ -51,13 +55,12 @@ .inserter-tabs__wrapper { overflow: hidden; + flex: 1; } .inserter-tabs__container { height: 100%; width: 100%; -} - -.inserter-tabs__item { - position: absolute; + flex: 1; + flex-direction: row; } diff --git a/packages/block-editor/src/components/inserter/tabs.native.js b/packages/block-editor/src/components/inserter/tabs.native.js index 06ab3bc859036e..73396ec63a6e9a 100644 --- a/packages/block-editor/src/components/inserter/tabs.native.js +++ b/packages/block-editor/src/components/inserter/tabs.native.js @@ -100,13 +100,7 @@ function InserterTabs( { > { tabs.map( ( { component: TabComponent }, index ) => ( - + ); + const showDragIndicator = () => { + // if iOS or not fullscreen show the drag indicator + if ( Platform.OS === 'ios' || ! this.state.isFullScreen ) { + return true; + } + + // Otherwise check the allowDragIndicator + return this.props.allowDragIndicator; + }; + return ( - { ! ( Platform.OS === 'android' && isFullScreen ) && ( + { showDragIndicator() && ( ) } { ! hideHeader && getHeader() } diff --git a/packages/components/src/search-control/index.native.js b/packages/components/src/search-control/index.native.js index 61241f77fb810b..6797c0c6a0bd95 100644 --- a/packages/components/src/search-control/index.native.js +++ b/packages/components/src/search-control/index.native.js @@ -8,6 +8,7 @@ import { TouchableOpacity, Platform, useColorScheme, + Keyboard, } from 'react-native'; /** @@ -116,12 +117,18 @@ function SearchControl( { setCurrentStyles( futureStyles ); }, [ isActive, isDark ] ); - useEffect( - () => () => { + useEffect( () => { + const keyboardHideSubscription = Keyboard.addListener( + 'keyboardDidHide', + () => { + onCancel(); + } + ); + return () => { clearTimeout( onCancelTimer.current ); - }, - [] - ); + keyboardHideSubscription.remove(); + }; + }, [] ); const { 'search-control__container': containerStyle, diff --git a/packages/components/src/search-control/platform-style.android.scss b/packages/components/src/search-control/platform-style.android.scss index 1d774af4009000..f357c7a4e52fc9 100644 --- a/packages/components/src/search-control/platform-style.android.scss +++ b/packages/components/src/search-control/platform-style.android.scss @@ -1,10 +1,7 @@ -.search-control__container { - height: 40px; -} - .search-control__container--active { border-bottom-color: $light-gray-500; border-bottom-width: 1px; + margin-bottom: 0; margin-left: 0; margin-right: 0; border-radius: 0; diff --git a/packages/components/src/search-control/platform-style.ios.scss b/packages/components/src/search-control/platform-style.ios.scss index f083df3e248ff4..34884b11b51bcd 100644 --- a/packages/components/src/search-control/platform-style.ios.scss +++ b/packages/components/src/search-control/platform-style.ios.scss @@ -1,6 +1,3 @@ -.search-control__container { - height: 40px; -} .search-control__container--active { margin-right: 0; } diff --git a/packages/components/src/search-control/style.native.scss b/packages/components/src/search-control/style.native.scss index 18f2b7490719ae..5cac32eddd4a72 100644 --- a/packages/components/src/search-control/style.native.scss +++ b/packages/components/src/search-control/style.native.scss @@ -1,8 +1,6 @@ .search-control__container { - height: 46px; + height: 48px; margin: 0 16px $grid-unit-10; - flex-direction: row; - justify-content: space-between; } .search-control__inner-container { From 3f2593fbfd61aff7bdbc2d177994c3b562f92bcc Mon Sep 17 00:00:00 2001 From: Vicente Canales <1157901+vcanales@users.noreply.github.com> Date: Wed, 25 Aug 2021 16:53:36 -0400 Subject: [PATCH 042/214] add example for debugging e2e tests on vscode (#29788) --- .vscode/launch-example.json | 19 ++++++++++- packages/e2e-tests/README.md | 62 ++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/.vscode/launch-example.json b/.vscode/launch-example.json index 3e333f450a0013..67b7a8859eb208 100644 --- a/.vscode/launch-example.json +++ b/.vscode/launch-example.json @@ -9,6 +9,23 @@ "pathMappings": { "/var/www/html/wp-content/plugins/gutenberg": "${workspaceRoot}/" } + }, + { + "type": "node", + "request": "launch", + "name": "Debug current e2e test", + "program": "${workspaceRoot}/node_modules/@wordpress/scripts/bin/wp-scripts.js", + "args": [ + "test-e2e", + "--config=${workspaceRoot}/packages/e2e-tests/jest.config.js", + "--verbose=true", + "--runInBand", + "--watch", + "${file}" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "trace": "all" } ] -} \ No newline at end of file +} diff --git a/packages/e2e-tests/README.md b/packages/e2e-tests/README.md index a322b2e40cc489..5c24e95f8a62d6 100644 --- a/packages/e2e-tests/README.md +++ b/packages/e2e-tests/README.md @@ -10,6 +10,68 @@ Install the module npm install @wordpress/e2e-tests --save-dev ``` +## Running tests + +The following commands are available on the Gutenberg repo: + +```json +{ + "test-e2e": "wp-scripts test-e2e --config packages/e2e-tests/jest.config.js", + "test-e2e:debug": "wp-scripts --inspect-brk test-e2e --config packages/e2e-tests/jest.config.js --puppeteer-devtools", + "test-e2e:watch": "npm run test-e2e -- --watch", +} +``` + +### Run all available tests +```bash +npm run test-e2e +``` +### Run all available tests and listen for changes. +```bash +npm run test-e2e:watch +``` + +### Run a specific test file +```bash +npm run test-e2e -- packages/e2e-test/ +# Or, in order to watch for changes: +npm run test-e2e:watch -- packages/e2e-test/ +``` +### Debugging + +Makes e2e tests available to debug in a Chrome Browser. +```bash +npm run test-e2e:debug +``` +After running the command, tests will be available for debugging in Chrome by going to chrome://inspect/#devices and clicking `inspect` under the path to `/test-e2e.js`. + +#### Debugging in `vscode` + +Debugging in a Chrome browser can be replaced with `vscode`'s debugger by adding the following configuration to `.vscode/launch.json`: + +```json +{ + "type": "node", + "request": "launch", + "name": "Debug current e2e test", + "program": "${workspaceRoot}/node_modules/@wordpress/scripts/bin/wp-scripts.js", + "args": [ + "test-e2e", + "--config=${workspaceRoot}/packages/e2e-tests/jest.config.js", + "--verbose=true", + "--runInBand", + "--watch", + "${file}" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "trace": "all" + } +``` + +This will run jest, targetting the spec file currently open in the editor. `vscode`'s debugger can now be used to add breakpoints and inspect tests as you would in Chrome DevTools. + + **Note**: This package requires Node.js 12.0.0 or later. It is not compatible with older versions.

Code is Poetry.

From fe7c73d7a081db2bd27dc6f2e22af87e4b7a8cf2 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Thu, 26 Aug 2021 10:21:20 +0200 Subject: [PATCH 043/214] Refactor navigation block to use generic classnames. (#34171) * Refactor navigation block to use generic classnames. * Address feedback. --- packages/base-styles/_z-index.scss | 4 +- .../src/navigation-link/editor.scss | 20 ++-- .../block-library/src/navigation/editor.scss | 6 +- .../block-library/src/navigation/style.scss | 106 ++++++------------ .../block-library/src/page-list/style.scss | 2 +- .../src/components/editor/style.scss | 20 ++-- 6 files changed, 61 insertions(+), 97 deletions(-) diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index de9ec9d6917e56..5b7ddc1a0f0e14 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -43,8 +43,8 @@ $z-layers: ( ".wp-block-template-part__placeholder-preview-filter-input": 1, // Navigation menu dropdown. - ".has-child .wp-block-navigation-link__container": 28, - ".has-child:hover .wp-block-navigation-link__container": 29, + ".has-child .wp-block-navigation__submenu-container": 28, + ".has-child:hover .wp-block-navigation__submenu-container": 29, // Active pill button ".components-button {:focus or .is-primary}": 1, diff --git a/packages/block-library/src/navigation-link/editor.scss b/packages/block-library/src/navigation-link/editor.scss index a720088d055dd8..e11e9c949381c3 100644 --- a/packages/block-library/src/navigation-link/editor.scss +++ b/packages/block-library/src/navigation-link/editor.scss @@ -1,5 +1,5 @@ // Normalize Link and edit containers, to look mostly the same. -.wp-block-navigation-link__container { +.wp-block-navigation__submenu-container { border-radius: 0; // Make it the same height as the appender to prevent a jump. @@ -15,22 +15,20 @@ .wp-block-navigation .has-child { cursor: pointer; - .submenu-container, - .wp-block-navigation-link__container { - z-index: z-index(".has-child .wp-block-navigation-link__container"); + .wp-block-navigation__submenu-container { + z-index: z-index(".has-child .wp-block-navigation__submenu-container"); } &:hover { - .submenu-container, - .wp-block-navigation-link__container { - z-index: z-index(".has-child:hover .wp-block-navigation-link__container"); + .wp-block-navigation__submenu-container { + z-index: z-index(".has-child:hover .wp-block-navigation__submenu-container"); } } // Show on editor selected, even if on frontend it only stays open on focus-within. &.is-selected, &.has-child-selected { - > .wp-block-navigation-link__container { + > .wp-block-navigation__submenu-container { // We use important here because if the parent block is selected and submenus are present, they should always be visible. visibility: visible !important; opacity: 1 !important; @@ -44,11 +42,11 @@ */ .wp-block-navigation-link { - .wp-block-navigation-link__container { + .wp-block-navigation__submenu-container { display: block; } - .wp-block-navigation-link__content { + .wp-block-navigation-item__content { cursor: text; } @@ -96,7 +94,7 @@ } // This needs extra specificity. - &.wp-block-navigation-link__content { + &.wp-block-navigation-item__content { cursor: pointer; } } diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss index fff28e42151ab9..47fa26ce4920cc 100644 --- a/packages/block-library/src/navigation/editor.scss +++ b/packages/block-library/src/navigation/editor.scss @@ -37,7 +37,7 @@ // Only show the flyout on hover if the parent menu item is selected. .wp-block-navigation:not(.is-selected):not(.has-child-selected) .has-child:hover { - > .wp-block-navigation-link__container { + > .wp-block-navigation__submenu-container { opacity: 0; visibility: hidden; } @@ -47,7 +47,7 @@ .has-child { &.is-selected, &.has-child-selected { - > .wp-block-navigation-link__container { + > .wp-block-navigation__submenu-container { display: flex; opacity: 1; visibility: visible; @@ -58,7 +58,7 @@ // Show a submenu when generally dragging (is-dragging-components-draggable) if that // submenu has children (has-child) and is being dragged within (is-dragging-within). .is-dragging-components-draggable .has-child.is-dragging-within { - > .wp-block-navigation-link__container { + > .wp-block-navigation__submenu-container { opacity: 1; visibility: visible; } diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index b37e4dbd0244f4..59edade44dcfa3 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -1,8 +1,11 @@ // Navigation block and menu item styles. -// This also styles navigation links inside the Page List block, -// as that block is meant to behave as menu items when leveraged. -// The CSS lives here so that it is output even if you only use a -// Page List block inside your navigation block. +// These styles also affect the Page List block when used inside your navigation block. +// +// Classes: +// - .wp-block-navigation__submenu-container targets submenu containers. +// - .wp-block-navigation-item targets the menu item itself. +// - .wp-block-navigation-item__content targets the link inside a menu item. +// - .wp-block-navigation__submenu-icon targets the chevron icon indicating submenus. .wp-block-navigation { position: relative; @@ -27,13 +30,12 @@ } // Menu item container. - .wp-block-pages-list__item, - .wp-block-navigation-link { + .wp-block-navigation-item { display: flex; align-items: center; position: relative; - .wp-block-navigation-link__container:empty { + .wp-block-navigation__submenu-container:empty { display: none; } } @@ -50,9 +52,8 @@ // Force links to inherit text decoration applied to navigation block. // The extra selector adds specificity to ensure it works when user-set. &[style*="text-decoration"] { - .wp-block-pages-list__item, - .wp-block-navigation-link__container, - .wp-block-navigation-link { + .wp-block-navigation-item, + .wp-block-navigation__submenu-container { text-decoration: inherit; } @@ -78,8 +79,7 @@ } // Submenu indicator. - .wp-block-page-list__submenu-icon, - .wp-block-navigation-link__submenu-icon { + .wp-block-navigation__submenu-icon { align-self: center; height: inherit; line-height: 0; @@ -99,7 +99,7 @@ // We use :where to keep specificity minimal, yet still scope it to only the navigation block. // That way if padding is set in theme.json, it still wins. // https://css-tricks.com/almanac/selectors/w/where/ - :where(.submenu-container, .wp-block-navigation-link__container) { + :where(.wp-block-navigation__submenu-container) { background-color: inherit; color: inherit; position: absolute; @@ -115,15 +115,13 @@ visibility: hidden; // Submenu items. - > .wp-block-pages-list__item, - > .wp-block-navigation-link { + > .wp-block-navigation-item { > a { display: flex; flex-grow: 1; // Right-align the chevron in submenus. - .wp-block-page-list__submenu-icon, - .wp-block-navigation-link__submenu-icon { + .wp-block-navigation__submenu-icon { margin-right: 0; margin-left: auto; } @@ -143,8 +141,7 @@ // Nested submenus sit to the left on large breakpoints. // On smaller breakpoints, they open vertically, accordion-style. @include break-medium { - .submenu-container, - .wp-block-navigation-link__container { + .wp-block-navigation__submenu-container { left: 100%; top: -1px; // Border width. @@ -161,8 +158,7 @@ } // Reset the submenu indicator for horizontal flyouts. - .wp-block-page-list__submenu-icon svg, - .wp-block-navigation-link__submenu-icon svg { + .wp-block-navigation__submenu-icon svg { transform: rotate(-90deg); } } @@ -174,47 +170,26 @@ // Custom menu items. // Show submenus on hover. - &:hover > .wp-block-navigation-link__container { + &:hover > .wp-block-navigation__submenu-container { visibility: visible; opacity: 1; } // Keep submenus open when focus is within. - &:focus-within > .wp-block-navigation-link__container { + &:focus-within > .wp-block-navigation__submenu-container { visibility: visible; opacity: 1; } - - // Page list menu items. - &:hover { - cursor: pointer; - - > .submenu-container { - visibility: visible; - opacity: 1; - } - } - - &:focus-within { - cursor: pointer; - - > .submenu-container { - visibility: visible; - opacity: 1; - } - } } // Submenu indentation when there's a background. -.wp-block-navigation.has-background .has-child .submenu-container, -.wp-block-navigation.has-background .has-child .wp-block-navigation-link__container { +.wp-block-navigation.has-background .has-child .wp-block-navigation__submenu-container { left: 0; top: 100%; // There's no border on submenus when there are backgrounds. @include break-medium { - .submenu-container, - .wp-block-navigation-link__container { + .wp-block-navigation__submenu-container { left: 100%; top: 0; } @@ -229,8 +204,8 @@ // Menu items with no background. .wp-block-page-list, -.wp-block-page-list > .wp-block-pages-list__item, -.wp-block-navigation__container > .wp-block-navigation-link { +.wp-block-page-list > .wp-block-navigation-item, +.wp-block-navigation__container > .wp-block-navigation-item { margin: 0 2em 0 0; // Margin of right-most margin should be zero, for right aligned or justified items. @@ -245,8 +220,8 @@ // https://css-tricks.com/almanac/selectors/w/where/ .wp-block-navigation:where(.has-background) { .wp-block-page-list, - .wp-block-page-list > .wp-block-pages-list__item, - .wp-block-navigation__container > .wp-block-navigation-link { + .wp-block-page-list > .wp-block-navigation-item, + .wp-block-navigation__container > .wp-block-navigation-item { margin: 0 0.5em 0 0; // Don't show right margin on the last child. @@ -277,7 +252,7 @@ } // Provide a default padding for submenus who should always have some, regardless of the top level menu items. -.wp-block-navigation :where(.submenu-container, .wp-block-navigation-link__container) a { +.wp-block-navigation :where(.wp-block-navigation__submenu-container) a { padding: 0.5em 1em; } @@ -295,14 +270,12 @@ .wp-block-navigation.items-justified-right > .wp-block-navigation__container .has-child { // First submenu. - .submenu-container, - .wp-block-navigation-link__container { + .wp-block-navigation__submenu-container { left: auto; right: 0; // Nested submenus. - .submenu-container, - .wp-block-navigation-link__container { + .wp-block-navigation__submenu-container { left: auto; right: 100%; } @@ -311,8 +284,7 @@ // Default background and font color. .wp-block-navigation:not(.has-background) { - .submenu-container, // This target items created by the Page List block. - .wp-block-navigation__container .wp-block-navigation-link__container { + .wp-block-navigation__submenu-container { // Set a background color for submenus so that they're not transparent. // NOTE TO DEVS - if refactoring this code, please double-check that // submenus have a default background color, this feature has regressed @@ -374,8 +346,7 @@ .is-vertical.items-justified-right > ul { align-items: flex-end; - .wp-block-navigation-link, - .wp-block-pages-list__item { + .wp-block-navigation-item { margin-right: 0; justify-content: flex-end; } @@ -432,10 +403,8 @@ // Remove background colors for items inside the overlay menu. // Has to be !important to override global styles. // @todo: We should revisit this so that the overlay colors can be applied, instead of the background. - .wp-block-pages-list__item .submenu-container, - .wp-block-navigation-link .wp-block-navigation-link__container, - .wp-block-pages-list__item, - .wp-block-navigation-link { + .wp-block-navigation-item .wp-block-navigation__submenu-container, + .wp-block-navigation-item { color: inherit !important; background: transparent !important; } @@ -456,8 +425,7 @@ &.is-menu-open { // Override breakpoint-inherited submenu rules. - .submenu-container.submenu-container.submenu-container.submenu-container, - .wp-block-navigation-link__container.wp-block-navigation-link__container.wp-block-navigation-link__container.wp-block-navigation-link__container { + .wp-block-navigation__submenu-container.wp-block-navigation__submenu-container.wp-block-navigation__submenu-container.wp-block-navigation__submenu-container { left: 0; } } @@ -537,13 +505,12 @@ // Always show submenus fully expanded inside the modal menu. .wp-block-navigation .wp-block-navigation__responsive-container.is-menu-open { - .wp-block-page-list__submenu-icon, - .wp-block-navigation-link__submenu-icon { + .wp-block-navigation__submenu-icon { display: none; } .has-child .submenu-container, - .has-child .wp-block-navigation-link__container { + .has-child .wp-block-navigation__submenu-container { position: relative; opacity: 1; visibility: visible; @@ -552,8 +519,7 @@ border: none; } - .wp-block-navigation-link, - .wp-block-pages-list__item { + .wp-block-navigation-item { flex-direction: column; align-items: flex-start; } diff --git a/packages/block-library/src/page-list/style.scss b/packages/block-library/src/page-list/style.scss index 8b2355c8f97403..806c075319eaac 100644 --- a/packages/block-library/src/page-list/style.scss +++ b/packages/block-library/src/page-list/style.scss @@ -8,7 +8,7 @@ // Menu items generated by the page list do not get `has-[x]-background-color`, // and must therefore inherit from the parent. - .wp-block-pages-list__item { + .wp-block-navigation-item { background-color: inherit; } diff --git a/packages/edit-navigation/src/components/editor/style.scss b/packages/edit-navigation/src/components/editor/style.scss index eaa1ae7e3221e4..74077954e1dc35 100644 --- a/packages/edit-navigation/src/components/editor/style.scss +++ b/packages/edit-navigation/src/components/editor/style.scss @@ -34,7 +34,7 @@ display: block; // Show submenus on click. - > .wp-block-navigation-link__container { + > .wp-block-navigation__submenu-container { // This unsets some styles inherited from the block, meant to only show submenus on click, not hover, when inside the editor. opacity: 1; visibility: visible; @@ -44,8 +44,8 @@ } // Fix focus outlines. - &.is-selected > .wp-block-navigation-link__content, - &.is-selected:hover > .wp-block-navigation-link__content { + &.is-selected > .wp-block-navigation-item__content, + &.is-selected:hover > .wp-block-navigation-item__content { box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); } @@ -59,7 +59,7 @@ margin-right: 0; } - .wp-block-navigation-link__content.wp-block-navigation-link__content.wp-block-navigation-link__content { + .wp-block-navigation-item__content.wp-block-navigation-item__content.wp-block-navigation-item__content { padding: 0.5em 1em; margin-bottom: 6px; margin-right: 0; @@ -91,7 +91,7 @@ pointer-events: none; margin-right: 0; - .wp-block-pages-list__item { + .wp-block-navigation-item { color: $gray-700; margin-bottom: 6px; border-radius: $radius-block-ui; @@ -101,7 +101,7 @@ } // Submenu icon indicator. - .wp-block-navigation-link__submenu-icon { + .wp-block-navigation__submenu-icon { position: absolute; top: 6px; left: 0; @@ -118,8 +118,8 @@ } // Point downwards when open. - .is-selected.has-child > .wp-block-navigation-link__submenu-icon svg, - .has-child-selected.has-child > .wp-block-navigation-link__submenu-icon svg { + .is-selected.has-child > .wp-block-navigation__submenu-icon svg, + .has-child-selected.has-child > .wp-block-navigation__submenu-icon svg { transform: rotate(0deg); } @@ -131,7 +131,7 @@ // Override for deeply nested submenus. .has-child .wp-block-navigation__container .wp-block-navigation__container, - .has-child .wp-block-navigation__container .wp-block-navigation-link__container { + .has-child .wp-block-navigation__container .wp-block-navigation__submenu-container { left: auto; } @@ -139,7 +139,7 @@ // and adjust the spacing and submenu icon. .wp-block-navigation-link.has-child.is-editing { > .wp-block-navigation__container, - > .wp-block-navigation-link__container { + > .wp-block-navigation__submenu-container { opacity: 1; visibility: visible; position: relative; From d8808e866ff760766c9eacbeb347c769fd7659ae Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 26 Aug 2021 08:55:14 +0000 Subject: [PATCH 044/214] ESLint: Add useSelect to direct function calls list (#34301) * ESLint: Add useSelect to direct function calls list * Fix remaining error * Fix store variable names --- .../header/document-actions/index.js | 2 +- .../data-no-store-string-literals.js | 30 ++++++++++--------- .../rules/data-no-store-string-literals.js | 10 +++++-- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/edit-site/src/components/header/document-actions/index.js b/packages/edit-site/src/components/header/document-actions/index.js index fc9f592215df43..1844f198000b68 100644 --- a/packages/edit-site/src/components/header/document-actions/index.js +++ b/packages/edit-site/src/components/header/document-actions/index.js @@ -29,7 +29,7 @@ function getBlockDisplayText( block ) { } function useSecondaryText() { - const { getBlock } = useSelect( 'core/block-editor' ); + const { getBlock } = useSelect( blockEditorStore ); const activeEntityBlockId = useSelect( ( select ) => select( diff --git a/packages/eslint-plugin/rules/__tests__/data-no-store-string-literals.js b/packages/eslint-plugin/rules/__tests__/data-no-store-string-literals.js index ac74c7800a50a4..f7581ac0b14105 100644 --- a/packages/eslint-plugin/rules/__tests__/data-no-store-string-literals.js +++ b/packages/eslint-plugin/rules/__tests__/data-no-store-string-literals.js @@ -17,24 +17,25 @@ const ruleTester = new RuleTester( { const valid = [ // Callback functions - `import { createRegistrySelector } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; createRegistrySelector(( select ) => { select(store); });`, - `import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; useSelect(( select ) => { select(store); });`, - `import { withSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; withSelect(( select ) => { select(store); });`, - `import { withDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; withDispatch(( select ) => { select(store); });`, - `import { withDispatch as withDispatchAlias } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; withDispatchAlias(( select ) => { select(store); });`, + `import { createRegistrySelector } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; createRegistrySelector(( select ) => { select(coreStore); });`, + `import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; useSelect(( select ) => { select(coreStore); });`, + `import { withSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; withSelect(( select ) => { select(coreStore); });`, + `import { withDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; withDispatch(( select ) => { select(coreStore); });`, + `import { withDispatch as withDispatchAlias } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; withDispatchAlias(( select ) => { select(coreStore); });`, // Direct function calls - `import { useDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; useDispatch( store );`, - `import { dispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; dispatch( store );`, - `import { select } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; select( store );`, - `import { resolveSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; resolveSelect( store );`, - `import { resolveSelect as resolveSelectAlias } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; resolveSelectAlias( store );`, + `import { useDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; useDispatch( coreStore );`, + `import { dispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; dispatch( coreStore );`, + `import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; useSelect( coreStore );`, + `import { select } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; select( coreStore );`, + `import { resolveSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; resolveSelect( coreStore );`, + `import { resolveSelect as resolveSelectAlias } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; resolveSelectAlias( coreStore );`, // Object property function calls - `import { controls } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; controls.select( store );`, - `import { controls } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; controls.dispatch( store );`, - `import { controls } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; controls.resolveSelect( store );`, - `import { controls as controlsAlias } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; controlsAlias.resolveSelect( store );`, + `import { controls } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; controls.select( coreStore );`, + `import { controls } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; controls.dispatch( coreStore );`, + `import { controls } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; controls.resolveSelect( coreStore );`, + `import { controls as controlsAlias } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; controlsAlias.resolveSelect( coreStore );`, ]; const createSuggestionTestCase = ( code, output ) => ( { @@ -63,6 +64,7 @@ const invalid = [ // Direct function calls `import { useDispatch } from '@wordpress/data'; useDispatch( 'core' );`, `import { dispatch } from '@wordpress/data'; dispatch( 'core' );`, + `import { useSelect } from '@wordpress/data'; useSelect( 'core' );`, `import { select } from '@wordpress/data'; select( 'core' );`, `import { resolveSelect } from '@wordpress/data'; resolveSelect( 'core' );`, `import { resolveSelect as resolveSelectAlias } from '@wordpress/data'; resolveSelectAlias( 'core' );`, diff --git a/packages/eslint-plugin/rules/data-no-store-string-literals.js b/packages/eslint-plugin/rules/data-no-store-string-literals.js index 72dfc97654f2a2..805a9cc6bd610b 100644 --- a/packages/eslint-plugin/rules/data-no-store-string-literals.js +++ b/packages/eslint-plugin/rules/data-no-store-string-literals.js @@ -78,9 +78,13 @@ function collectAllNodesFromDirectFunctionCalls( context, node ) { const specifiers = node.specifiers.filter( ( specifier ) => specifier.imported && - [ 'useDispatch', 'dispatch', 'select', 'resolveSelect' ].includes( - specifier.imported.name - ) + [ + 'useDispatch', + 'dispatch', + 'useSelect', + 'select', + 'resolveSelect', + ].includes( specifier.imported.name ) ); const references = getReferences( context, specifiers ); const possibleCallExpressionNodes = references From 39806aa26f55b8b1ea246fcf56ba8afe7541bf64 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 26 Aug 2021 10:06:32 +0100 Subject: [PATCH 045/214] Improve the getBlock and getBlocks performance (#34241) --- .../data/data-core-block-editor.md | 8 +- .../block-list/block-list-item.native.js | 9 +- .../src/components/block-list/block.native.js | 4 +- .../components/block-tools/block-popover.js | 5 +- .../block-tools/block-selection-button.js | 6 +- .../src/components/rich-text/index.native.js | 5 +- packages/block-editor/src/store/actions.js | 2 + packages/block-editor/src/store/reducer.js | 353 +++++++++++------- packages/block-editor/src/store/selectors.js | 117 +----- .../block-editor/src/store/test/reducer.js | 230 +++++++----- .../block-editor/src/store/test/selectors.js | 249 +++++------- .../convert-to-regular.js | 10 +- packages/editor/src/store/selectors.js | 7 - 13 files changed, 478 insertions(+), 527 deletions(-) diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index 3cc75b50481683..2f5b5193452558 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -281,12 +281,6 @@ Returns all block objects for the current post being edited as an array in the order they appear in the post. Note that this will exclude child blocks of nested inner block controllers. -Note: It's important to memoize this selector to avoid return a new instance -on each call. We use the block cache state for each top-level block of the -given clientID. This way, the selector only refreshes on changes to blocks -associated with the given entity, and does not refresh when changes are made -to blocks which are part of different inner block controllers. - _Parameters_ - _state_ `Object`: Editor state. @@ -1225,6 +1219,8 @@ Returns an action object used in signalling that blocks have been received. Unlike resetBlocks, these should be appended to the existing known set, not replacing. +Todo: This should be deprecated + _Parameters_ - _blocks_ `Object[]`: Array of block objects. diff --git a/packages/block-editor/src/components/block-list/block-list-item.native.js b/packages/block-editor/src/components/block-list/block-list-item.native.js index fae95956fa735a..0abe3c213d4a90 100644 --- a/packages/block-editor/src/components/block-list/block-list-item.native.js +++ b/packages/block-editor/src/components/block-list/block-list-item.native.js @@ -199,7 +199,7 @@ export default compose( [ isBlockInsertionPointVisible, getSettings, getBlockParents, - __unstableGetBlockWithoutInnerBlocks, + getBlock, } = select( blockEditorStore ); const blockClientIds = getBlockOrder( rootClientId ); @@ -225,14 +225,11 @@ export default compose( [ const isReadOnly = getSettings().readOnly; - const block = __unstableGetBlockWithoutInnerBlocks( clientId ); - const { attributes, name } = block || {}; + const { attributes, name } = getBlock( clientId ); const { align } = attributes || {}; const parents = getBlockParents( clientId, true ); const hasParents = !! parents.length; - const parentBlock = hasParents - ? __unstableGetBlockWithoutInnerBlocks( parents[ 0 ] ) - : {}; + const parentBlock = hasParents ? getBlock( parents[ 0 ] ) : {}; const { align: parentBlockAlignment } = parentBlock?.attributes || {}; const { name: parentBlockName } = parentBlock || {}; diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 3f700ec22a2920..b7543dc7977671 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -305,7 +305,7 @@ export default compose( [ getBlockIndex, getSettings, isBlockSelected, - __unstableGetBlockWithoutInnerBlocks, + getBlock, getSelectedBlockClientId, getLowestCommonAncestorWithSelectedBlock, getBlockParents, @@ -315,7 +315,7 @@ export default compose( [ const order = getBlockIndex( clientId, rootClientId ); const isSelected = isBlockSelected( clientId ); const isInnerBlockSelected = hasSelectedInnerBlock( clientId ); - const block = __unstableGetBlockWithoutInnerBlocks( clientId ); + const block = getBlock( clientId ); const { name, attributes, isValid } = block || {}; const blockType = getBlockType( name || 'core/missing' ); diff --git a/packages/block-editor/src/components/block-tools/block-popover.js b/packages/block-editor/src/components/block-tools/block-popover.js index 59d9ae24a71d3a..3797f68edadffc 100644 --- a/packages/block-editor/src/components/block-tools/block-popover.js +++ b/packages/block-editor/src/components/block-tools/block-popover.js @@ -284,7 +284,7 @@ function wrapperSelector( select ) { getSelectedBlockClientId, getFirstMultiSelectedBlockClientId, getBlockRootClientId, - __unstableGetBlockWithoutInnerBlocks, + getBlock, getBlockParents, __experimentalGetBlockListSettingsForBlocks, } = select( blockEditorStore ); @@ -296,8 +296,7 @@ function wrapperSelector( select ) { return; } - const { name, attributes = {}, isValid } = - __unstableGetBlockWithoutInnerBlocks( clientId ) || {}; + const { name, attributes = {}, isValid } = getBlock( clientId ) || {}; const blockParentsClientIds = getBlockParents( clientId ); // Get Block List Settings for all ancestors of the current Block clientId diff --git a/packages/block-editor/src/components/block-tools/block-selection-button.js b/packages/block-editor/src/components/block-tools/block-selection-button.js index 12b4bb2d0407ad..8eb9a5b6950680 100644 --- a/packages/block-editor/src/components/block-tools/block-selection-button.js +++ b/packages/block-editor/src/components/block-tools/block-selection-button.js @@ -54,15 +54,13 @@ function BlockSelectionButton( { clientId, rootClientId, blockElement } ) { const selected = useSelect( ( select ) => { const { - __unstableGetBlockWithoutInnerBlocks, + getBlock, getBlockIndex, hasBlockMovingClientId, getBlockListSettings, } = select( blockEditorStore ); const index = getBlockIndex( clientId, rootClientId ); - const { name, attributes } = __unstableGetBlockWithoutInnerBlocks( - clientId - ); + const { name, attributes } = getBlock( clientId ); const blockMovingMode = hasBlockMovingClientId(); return { index, diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 5c9af75b2796fb..85c1e647853007 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -125,7 +125,7 @@ function RichTextWrapper( getSelectionEnd, getSettings, didAutomaticChange, - __unstableGetBlockWithoutInnerBlocks, + getBlock, isMultiSelecting, hasMultiSelection, } = select( blockEditorStore ); @@ -149,8 +149,7 @@ function RichTextWrapper( // If the block of this RichText is unmodified then it's a candidate for replacing when adding a new block. // In order to fix https://github.com/wordpress-mobile/gutenberg-mobile/issues/1126, let's blur on unmount in that case. // This apparently assumes functionality the BlockHlder actually - const block = - clientId && __unstableGetBlockWithoutInnerBlocks( clientId ); + const block = clientId && getBlock( clientId ); const shouldBlurOnUnmount = block && isSelected && isUnmodifiedDefaultBlock( block ); extraProps = { diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 23ef155e31aff3..817953f3aef242 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -151,6 +151,8 @@ export function resetSelection( * Unlike resetBlocks, these should be appended to the existing known set, not * replacing. * + * Todo: This should be deprecated + * * @param {Object[]} blocks Array of block objects. * * @return {Object} Action object. diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index db5477661d50c2..4c18f041c7948f 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -13,7 +13,6 @@ import { isEqual, isEmpty, identity, - difference, omitBy, pickBy, } from 'lodash'; @@ -215,150 +214,211 @@ export function isUpdatingSameBlockAttribute( action, lastAction ) { ); } -/** - * Utility returning an object with an empty object value for each key. - * - * @param {Array} objectKeys Keys to fill. - * @return {Object} Object filled with empty object as values for each clientId. - */ -const fillKeysWithEmptyObject = ( objectKeys ) => { - return objectKeys.reduce( ( result, key ) => { - result[ key ] = {}; - return result; - }, {} ); -}; +function buildBlockTree( state, blocks ) { + const result = {}; + const stack = [ ...blocks ]; + const flattenedBlocks = [ ...blocks ]; + while ( stack.length ) { + const block = stack.shift(); + stack.push( ...block.innerBlocks ); + flattenedBlocks.push( ...block.innerBlocks ); + } + // Create objects before mutating them, that way it's always defined. + for ( const block of flattenedBlocks ) { + result[ block.clientId ] = {}; + } + for ( const block of flattenedBlocks ) { + result[ block.clientId ] = Object.assign( result[ block.clientId ], { + ...state.byClientId[ block.clientId ], + attributes: state.attributes[ block.clientId ], + innerBlocks: block.innerBlocks.map( + ( subBlock ) => result[ subBlock.clientId ] + ), + } ); + } + + return result; +} + +function updateParentInnerBlocksInTree( state, tree, updatedClientIds ) { + const clientIds = new Set( [] ); + const controlledParents = new Set(); + for ( const clientId of updatedClientIds ) { + let current = clientId; + do { + if ( state.controlledInnerBlocks[ current ] ) { + controlledParents.add( current ); + // If we reach a controlled parent, break out of the loop. + break; + } else { + clientIds.add( current ); + } + // Should stop on controlled blocks. + current = state.parents[ current ]; + } while ( current !== undefined ); + } + + // To make sure the order of assignments doesn't matter, + // we first create empty objects and mutates the inner blocks later. + for ( const clientId of clientIds ) { + tree[ clientId ] = { + ...tree[ clientId ], + }; + } + for ( const clientId of clientIds ) { + tree[ clientId ].innerBlocks = ( state.order[ clientId ] || [] ).map( + ( subClientId ) => tree[ subClientId ] + ); + } + // Controlled parent blocks, need a dedicated key for their inner blocks + // to be used when doing getBlocks( controlledBlockClientId ). + for ( const clientId of controlledParents ) { + tree[ 'controlled||' + clientId ] = { + innerBlocks: ( state.order[ clientId ] || [] ).map( + ( subClientId ) => tree[ subClientId ] + ), + }; + } + + return tree; +} /** - * Higher-order reducer intended to compute a cache key for each block in the post. - * A new instance of the cache key (empty object) is created each time the block object - * needs to be refreshed (for any change in the block or its children). + * Higher-order reducer intended to compute full block objects key for each block in the post. + * This is a denormalization to optimize the performance of the getBlock selectors and avoid + * recomputing the block objects and avoid heavy memoization. * * @param {Function} reducer Original reducer function. * * @return {Function} Enhanced reducer function. */ -const withBlockCache = ( reducer ) => ( state = {}, action ) => { +const withBlockTree = ( reducer ) => ( state = {}, action ) => { const newState = reducer( state, action ); if ( newState === state ) { return state; } - newState.cache = state.cache ? state.cache : {}; - - /** - * For each clientId provided, traverses up parents, adding the provided clientIds - * and each parent's clientId to the returned array. - * - * When calling this function consider that it uses the old state, so any state - * modifications made by the `reducer` will not be present. - * - * @param {Array} clientIds an Array of block clientIds. - * - * @return {Array} The provided clientIds and all of their parent clientIds. - */ - const getBlocksWithParentsClientIds = ( clientIds ) => { - return clientIds.reduce( ( result, clientId ) => { - let current = clientId; - do { - result.push( current ); - current = state.parents[ current ]; - } while ( current && ! state.controlledInnerBlocks[ current ] ); - return result; - }, [] ); - }; + newState.tree = state.tree ? state.tree : {}; switch ( action.type ) { - case 'RESET_BLOCKS': - newState.cache = mapValues( - flattenBlocks( action.blocks ), - () => ( {} ) - ); - break; case 'RECEIVE_BLOCKS': case 'INSERT_BLOCKS': { - const updatedBlockUids = keys( flattenBlocks( action.blocks ) ); - if ( - action.rootClientId && - ! state.controlledInnerBlocks[ action.rootClientId ] - ) { - updatedBlockUids.push( action.rootClientId ); - } - newState.cache = { - ...newState.cache, - ...fillKeysWithEmptyObject( - getBlocksWithParentsClientIds( updatedBlockUids ) - ), - }; + const subTree = buildBlockTree( newState, action.blocks ); + newState.tree = updateParentInnerBlocksInTree( + newState, + { + ...newState.tree, + ...subTree, + }, + action.rootClientId ? [ action.rootClientId ] : [ '' ] + ); break; } case 'UPDATE_BLOCK': - newState.cache = { - ...newState.cache, - ...fillKeysWithEmptyObject( - getBlocksWithParentsClientIds( [ action.clientId ] ) - ), - }; + newState.tree = updateParentInnerBlocksInTree( + newState, + { + ...newState.tree, + [ action.clientId ]: { + ...newState.byClientId[ action.clientId ], + attributes: newState.attributes[ action.clientId ], + }, + }, + [ action.clientId ] + ); break; - case 'UPDATE_BLOCK_ATTRIBUTES': - newState.cache = { - ...newState.cache, - ...fillKeysWithEmptyObject( - getBlocksWithParentsClientIds( action.clientIds ) - ), - }; + case 'UPDATE_BLOCK_ATTRIBUTES': { + const newSubTree = action.clientIds.reduce( + ( result, clientId ) => { + result[ clientId ] = { + ...newState.tree[ clientId ], + attributes: newState.attributes[ clientId ], + }; + return result; + }, + {} + ); + newState.tree = updateParentInnerBlocksInTree( + newState, + { + ...newState.tree, + ...newSubTree, + }, + action.clientIds + ); break; - case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': - const parentClientIds = fillKeysWithEmptyObject( - getBlocksWithParentsClientIds( action.replacedClientIds ) + } + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': { + const subTree = buildBlockTree( newState, action.blocks ); + newState.tree = updateParentInnerBlocksInTree( + newState, + { + ...omit( + newState.tree, + action.replacedClientIds.concat( + action.replacedClientIds.map( + ( clientId ) => 'controlled||' + clientId + ) + ) + ), + ...subTree, + }, + action.blocks.map( ( b ) => b.clientId ) ); - - newState.cache = { - ...omit( newState.cache, action.replacedClientIds ), - ...omit( parentClientIds, action.replacedClientIds ), - ...fillKeysWithEmptyObject( - keys( flattenBlocks( action.blocks ) ) - ), - }; break; + } case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': - newState.cache = { - ...omit( newState.cache, action.removedClientIds ), - ...fillKeysWithEmptyObject( - difference( - getBlocksWithParentsClientIds( action.clientIds ), - action.clientIds + const parentsOfRemovedBlocks = []; + for ( const clientId of action.clientIds ) { + if ( + state.parents[ clientId ] !== undefined && + ( state.parents[ clientId ] === '' || + newState.byClientId[ state.parents[ clientId ] ] ) + ) { + parentsOfRemovedBlocks.push( state.parents[ clientId ] ); + } + } + newState.tree = updateParentInnerBlocksInTree( + newState, + omit( + newState.tree, + action.removedClientIds.concat( + action.removedClientIds.map( + ( clientId ) => 'controlled||' + clientId + ) ) ), - }; + parentsOfRemovedBlocks + ); break; case 'MOVE_BLOCKS_TO_POSITION': { - const updatedBlockUids = [ ...action.clientIds ]; + const updatedBlockUids = []; if ( action.fromRootClientId ) { updatedBlockUids.push( action.fromRootClientId ); } if ( action.toRootClientId ) { updatedBlockUids.push( action.toRootClientId ); } - newState.cache = { - ...newState.cache, - ...fillKeysWithEmptyObject( - getBlocksWithParentsClientIds( updatedBlockUids ) - ), - }; + if ( ! action.fromRootClientId || ! action.fromRootClientId ) { + updatedBlockUids.push( '' ); + } + newState.tree = updateParentInnerBlocksInTree( + newState, + newState.tree, + updatedBlockUids + ); break; } case 'MOVE_BLOCKS_UP': case 'MOVE_BLOCKS_DOWN': { - const updatedBlockUids = []; - if ( action.rootClientId ) { - updatedBlockUids.push( action.rootClientId ); - } - newState.cache = { - ...newState.cache, - ...fillKeysWithEmptyObject( - getBlocksWithParentsClientIds( updatedBlockUids ) - ), - }; + const updatedBlockUids = [ + action.rootClientId ? action.rootClientId : '', + ]; + newState.tree = updateParentInnerBlocksInTree( + newState, + newState.tree, + updatedBlockUids + ); break; } case 'SAVE_REUSABLE_BLOCK_SUCCESS': { @@ -371,12 +431,21 @@ const withBlockCache = ( reducer ) => ( state = {}, action ) => { } ) ); - newState.cache = { - ...newState.cache, - ...fillKeysWithEmptyObject( - getBlocksWithParentsClientIds( updatedBlockUids ) - ), - }; + newState.tree = updateParentInnerBlocksInTree( + newState, + { + ...newState.tree, + ...updatedBlockUids.reduce( ( result, clientId ) => { + result[ clientId ] = { + ...newState.byClientId[ clientId ], + attributes: newState.attributes[ clientId ], + innerBlocks: newState.tree[ clientId ].innerBlocks, + }; + return result; + }, {} ), + }, + updatedBlockUids + ); } } @@ -531,21 +600,21 @@ const withInnerBlocksRemoveCascade = ( reducer ) => ( state, action ) => { * @return {Function} Enhanced reducer function. */ const withBlockReset = ( reducer ) => ( state, action ) => { - if ( state && action.type === 'RESET_BLOCKS' ) { + if ( action.type === 'RESET_BLOCKS' ) { /** * A list of client IDs associated with the top level entity (like a * post or template). It excludes the client IDs of blocks associated * with other entities, like inner block controllers or reusable blocks. */ const visibleClientIds = getNestedBlockClientIds( - state.order, + state?.order ?? {}, '', - state.controlledInnerBlocks + state?.controlledInnerBlocks ?? {} ); // pickBy returns only the truthy values from controlledInnerBlocks const controlledInnerBlocks = Object.keys( - pickBy( state.controlledInnerBlocks ) + pickBy( state?.controlledInnerBlocks ?? {} ) ); /** @@ -569,35 +638,43 @@ const withBlockReset = ( reducer ) => ( state, action ) => { * new value was used, template parts would disappear from the editor * whenever you try to undo a change in the top level entity. */ - return { + const newState = { ...state, byClientId: { - ...omit( state.byClientId, visibleClientIds ), + ...omit( state?.byClientId, visibleClientIds ), ...getFlattenedBlocksWithoutAttributes( action.blocks ), }, attributes: { - ...omit( state.attributes, visibleClientIds ), + ...omit( state?.attributes, visibleClientIds ), ...getFlattenedBlockAttributes( action.blocks ), }, order: { - ...omit( state.order, visibleClientIds ), + ...omit( state?.order, visibleClientIds ), ...omit( mapBlockOrder( action.blocks ), controlledInnerBlocks ), }, parents: { - ...omit( state.parents, visibleClientIds ), + ...omit( state?.parents, visibleClientIds ), ...mapBlockParents( action.blocks ), }, - cache: { - ...omit( state.cache, visibleClientIds ), - ...omit( - mapValues( flattenBlocks( action.blocks ), () => ( {} ) ), - controlledInnerBlocks + controlledInnerBlocks: state?.controlledInnerBlocks || {}, + }; + + const subTree = buildBlockTree( newState, action.blocks ); + newState.tree = { + ...omit( state?.tree, visibleClientIds ), + ...subTree, + // Root + '': { + innerBlocks: action.blocks.map( + ( subBlock ) => subTree[ subBlock.clientId ] ), }, }; + + return newState; } return reducer( state, action ); @@ -727,7 +804,7 @@ const withSaveReusableBlock = ( reducer ) => ( state, action ) => { export const blocks = flow( combineReducers, withSaveReusableBlock, // needs to be before withBlockCache - withBlockCache, // needs to be before withInnerBlocksRemoveCascade + withBlockTree, // needs to be before withInnerBlocksRemoveCascade withInnerBlocksRemoveCascade, withReplaceInnerBlocks, // needs to be after withInnerBlocksRemoveCascade withBlockReset, @@ -736,9 +813,6 @@ export const blocks = flow( )( { byClientId( state = {}, action ) { switch ( action.type ) { - case 'RESET_BLOCKS': - return getFlattenedBlocksWithoutAttributes( action.blocks ); - case 'RECEIVE_BLOCKS': case 'INSERT_BLOCKS': return { @@ -785,9 +859,6 @@ export const blocks = flow( attributes( state = {}, action ) { switch ( action.type ) { - case 'RESET_BLOCKS': - return getFlattenedBlockAttributes( action.blocks ); - case 'RECEIVE_BLOCKS': case 'INSERT_BLOCKS': return { @@ -873,15 +944,14 @@ export const blocks = flow( order( state = {}, action ) { switch ( action.type ) { - case 'RESET_BLOCKS': - return mapBlockOrder( action.blocks ); - - case 'RECEIVE_BLOCKS': + case 'RECEIVE_BLOCKS': { + const blockOrder = mapBlockOrder( action.blocks ); return { ...state, - ...omit( mapBlockOrder( action.blocks ), '' ), + ...omit( blockOrder, '' ), + '': ( state?.[ '' ] || [] ).concat( blockOrder ), }; - + } case 'INSERT_BLOCKS': { const { rootClientId = '' } = action; const subState = state[ rootClientId ] || []; @@ -1049,9 +1119,6 @@ export const blocks = flow( // an optimization for the selectors which derive the ancestry of a block. parents( state = {}, action ) { switch ( action.type ) { - case 'RESET_BLOCKS': - return mapBlockParents( action.blocks ); - case 'RECEIVE_BLOCKS': return { ...state, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 61501dae210e85..d10a80d83e2451 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -132,30 +132,14 @@ export function getBlockAttributes( state, clientId ) { * * @return {Object} Parsed block object. */ -export const getBlock = createSelector( - ( state, clientId ) => { - const block = state.blocks.byClientId[ clientId ]; - if ( ! block ) { - return null; - } +export function getBlock( state, clientId ) { + const block = state.blocks.byClientId[ clientId ]; + if ( ! block ) { + return null; + } - return { - ...block, - attributes: getBlockAttributes( state, clientId ), - innerBlocks: areInnerBlocksControlled( state, clientId ) - ? EMPTY_ARRAY - : getBlocks( state, clientId ), - }; - }, - ( state, clientId ) => [ - // Normally, we'd have both `getBlockAttributes` dependencies and - // `getBlocks` (children) dependencies here but for performance reasons - // we use a denormalized cache key computed in the reducer that takes both - // the attributes and inner blocks into account. The value of the cache key - // is being changed whenever one of these dependencies is out of date. - state.blocks.cache[ clientId ], - ] -); + return state.blocks.tree[ clientId ]; +} export const __unstableGetBlockWithoutInnerBlocks = createSelector( ( state, clientId ) => { @@ -180,81 +164,18 @@ export const __unstableGetBlockWithoutInnerBlocks = createSelector( * the order they appear in the post. Note that this will exclude child blocks * of nested inner block controllers. * - * Note: It's important to memoize this selector to avoid return a new instance - * on each call. We use the block cache state for each top-level block of the - * given clientID. This way, the selector only refreshes on changes to blocks - * associated with the given entity, and does not refresh when changes are made - * to blocks which are part of different inner block controllers. - * * @param {Object} state Editor state. * @param {?string} rootClientId Optional root client ID of block list. * * @return {Object[]} Post blocks. */ -export const getBlocks = createSelector( - ( state, rootClientId ) => { - return map( getBlockOrder( state, rootClientId ), ( clientId ) => - getBlock( state, clientId ) - ); - }, - ( state, rootClientId ) => - map( - state.blocks.order[ rootClientId || '' ], - ( id ) => state.blocks.cache[ id ] - ) -); - -/** - * Similar to getBlock, except it will include the entire nested block tree as - * inner blocks. The normal getBlock selector will exclude sections of the block - * tree which belong to different entities. - * - * @param {Object} state Editor state. - * @param {string} clientId Client ID of the block to get. - * - * @return {Object} The block with all - */ -export const __unstableGetBlockWithBlockTree = createSelector( - ( state, clientId ) => { - const block = state.blocks.byClientId[ clientId ]; - if ( ! block ) { - return null; - } - - return { - ...block, - attributes: getBlockAttributes( state, clientId ), - innerBlocks: __unstableGetBlockTree( state, clientId ), - }; - }, - ( state ) => [ - state.blocks.byClientId, - state.blocks.order, - state.blocks.attributes, - ] -); - -/** - * Similar to getBlocks, except this selector returns the entire block tree - * represented in the block-editor store from the given root regardless of any - * inner block controllers. - * - * @param {Object} state Editor state. - * @param {?string} rootClientId Optional root client ID of block list. - * - * @return {Object[]} Post blocks. - */ -export const __unstableGetBlockTree = createSelector( - ( state, rootClientId = '' ) => - map( getBlockOrder( state, rootClientId ), ( clientId ) => - __unstableGetBlockWithBlockTree( state, clientId ) - ), - ( state ) => [ - state.blocks.byClientId, - state.blocks.order, - state.blocks.attributes, - ] -); +export function getBlocks( state, rootClientId ) { + const treeKey = + ! rootClientId || ! areInnerBlocksControlled( state, rootClientId ) + ? rootClientId || '' + : 'controlled||' + rootClientId; + return state.blocks.tree[ treeKey ]?.innerBlocks || EMPTY_ARRAY; +} /** * Returns a stripped down block object containing only its client ID, @@ -369,11 +290,11 @@ export const getBlocksByClientId = createSelector( map( castArray( clientIds ), ( clientId ) => getBlock( state, clientId ) ), - ( state ) => [ - state.blocks.byClientId, - state.blocks.order, - state.blocks.attributes, - ] + ( state, clientIds ) => + map( + castArray( clientIds ), + ( clientId ) => state.blocks.tree[ clientId ] + ) ); /** diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index b4ad1c12ec1980..2015ca50d00e13 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { values, noop } from 'lodash'; +import { values, noop, omit } from 'lodash'; import deepFreeze from 'deep-freeze'; /** @@ -236,7 +236,8 @@ describe( 'state', () => { chicken: '', 'chicken-child': 'chicken', }, - cache: { + tree: { + '': {}, chicken: {}, 'chicken-child': {}, }, @@ -258,7 +259,7 @@ describe( 'state', () => { const state = blocks( existingState, action ); - expect( state ).toEqual( { + expect( omit( state, [ 'tree' ] ) ).toEqual( { isPersistentChange: true, isIgnoredChange: false, byClientId: { @@ -289,14 +290,10 @@ describe( 'state', () => { [ newChildBlockId ]: 'chicken', chicken: '', }, - cache: { - chicken: {}, - [ newChildBlockId ]: {}, - }, controlledInnerBlocks: {}, } ); - expect( state.cache.chicken ).not.toBe( - existingState.cache.chicken + expect( state.tree.chicken ).not.toBe( + existingState.tree.chicken ); } ); @@ -319,7 +316,10 @@ describe( 'state', () => { parents: { chicken: '', }, - cache: { + tree: { + '': { + innerBlocks: [], + }, chicken: {}, }, controlledInnerBlocks: {}, @@ -340,7 +340,7 @@ describe( 'state', () => { const state = blocks( existingState, action ); - expect( state ).toEqual( { + expect( omit( state, [ 'tree' ] ) ).toEqual( { isPersistentChange: true, isIgnoredChange: false, byClientId: { @@ -371,15 +371,27 @@ describe( 'state', () => { [ newChildBlockId ]: 'chicken', chicken: '', }, - cache: { - chicken: {}, - [ newChildBlockId ]: {}, - }, controlledInnerBlocks: {}, } ); - expect( state.cache.chicken ).not.toBe( - existingState.cache.chicken + expect( state.tree.chicken ).not.toBe( + existingState.tree.chicken + ); + expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( + state.tree.chicken + ); + expect( state.tree.chicken.innerBlocks[ 0 ] ).toBe( + state.tree[ newChildBlockId ] ); + expect( state.tree[ newChildBlockId ] ).toEqual( { + clientId: newChildBlockId, + innerBlocks: [], + isValid: true, + name: 'core/test-child-block', + attributes: { + attr: false, + attr2: 'perfect', + }, + } ); } ); it( 'can replace multiple child blocks', () => { @@ -421,11 +433,7 @@ describe( 'state', () => { 'chicken-child': 'chicken', 'chicken-child-2': 'chicken', }, - cache: { - chicken: {}, - 'chicken-child': {}, - 'chicken-child-2': {}, - }, + tree: {}, controlledInnerBlocks: {}, } ); @@ -455,7 +463,7 @@ describe( 'state', () => { const state = blocks( existingState, action ); - expect( state ).toEqual( { + expect( omit( state, [ 'tree' ] ) ).toEqual( { isPersistentChange: true, isIgnoredChange: false, byClientId: { @@ -511,14 +519,31 @@ describe( 'state', () => { [ newChildBlockId2 ]: 'chicken', [ newChildBlockId3 ]: 'chicken', }, - cache: { - chicken: {}, - [ newChildBlockId1 ]: {}, - [ newChildBlockId2 ]: {}, - [ newChildBlockId3 ]: {}, - }, controlledInnerBlocks: {}, } ); + + expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( + state.tree.chicken + ); + expect( state.tree.chicken.innerBlocks[ 0 ] ).toBe( + state.tree[ newChildBlockId1 ] + ); + expect( state.tree.chicken.innerBlocks[ 1 ] ).toBe( + state.tree[ newChildBlockId2 ] + ); + expect( state.tree.chicken.innerBlocks[ 2 ] ).toBe( + state.tree[ newChildBlockId3 ] + ); + expect( state.tree[ newChildBlockId1 ] ).toEqual( { + innerBlocks: [], + clientId: newChildBlockId1, + name: 'core/test-child-block', + isValid: true, + attributes: { + attr: false, + attr2: 'perfect', + }, + } ); } ); it( 'can replace a child block that has other children', () => { @@ -556,10 +581,8 @@ describe( 'state', () => { 'chicken-child': 'chicken', 'chicken-grand-child': 'chicken-child', }, - cache: { + tree: { chicken: {}, - 'chicken-child': {}, - 'chicken-grand-child': {}, }, controlledInnerBlocks: {}, } ); @@ -576,7 +599,7 @@ describe( 'state', () => { const state = blocks( existingState, action ); - expect( state ).toEqual( { + expect( omit( state, [ 'tree' ] ) ).toEqual( { isPersistentChange: true, isIgnoredChange: false, byClientId: { @@ -604,16 +627,12 @@ describe( 'state', () => { chicken: '', [ newChildBlockId ]: 'chicken', }, - cache: { - chicken: {}, - [ newChildBlockId ]: {}, - }, controlledInnerBlocks: {}, } ); - // the cache key of the parent should be updated - expect( existingState.cache.chicken ).not.toBe( - state.cache.chicken + // the block object of the parent should be updated + expect( state.tree.chicken ).not.toBe( + existingState.tree.chicken ); } ); } ); @@ -628,7 +647,7 @@ describe( 'state', () => { parents: {}, isPersistentChange: true, isIgnoredChange: false, - cache: {}, + tree: {}, controlledInnerBlocks: {}, } ); } ); @@ -648,9 +667,13 @@ describe( 'state', () => { '': [ 'bananas' ], bananas: [], } ); - expect( state.cache ).toEqual( { - bananas: {}, + expect( state.tree.bananas ).toEqual( { + clientId: 'bananas', + innerBlocks: [], } ); + expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( + state.tree.bananas + ); } ); } ); @@ -674,10 +697,6 @@ describe( 'state', () => { apples: [], bananas: [ 'apples' ], } ); - expect( state.cache ).toEqual( { - bananas: {}, - apples: {}, - } ); } ); it( 'should insert block', () => { @@ -710,12 +729,17 @@ describe( 'state', () => { chicken: [], ribs: [], } ); - expect( state.cache ).toEqual( { - chicken: {}, - ribs: {}, + + expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( + state.tree.chicken + ); + expect( state.tree[ '' ].innerBlocks[ 1 ] ).toBe( state.tree.ribs ); + expect( state.tree.chicken ).toEqual( { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], } ); - // The cache key is the same because the block has not been modified. - expect( original.cache.chicken ).toBe( state.cache.chicken ); } ); it( 'should replace the block', () => { @@ -754,10 +778,16 @@ describe( 'state', () => { expect( state.parents ).toEqual( { wings: '', } ); - expect( state.cache ).toEqual( { - wings: {}, + expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( + state.tree.wings + ); + expect( state.tree.wings ).toEqual( { + clientId: 'wings', + name: 'core/freeform', + innerBlocks: [], } ); } ); + it( 'should replace the block and remove references to its inner blocks', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', @@ -797,8 +827,13 @@ describe( 'state', () => { expect( state.parents ).toEqual( { wings: '', } ); - expect( state.cache ).toEqual( { - wings: {}, + expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( + state.tree.wings + ); + expect( state.tree.wings ).toEqual( { + clientId: 'wings', + name: 'core/freeform', + innerBlocks: [], } ); } ); @@ -813,22 +848,12 @@ describe( 'state', () => { blocks: [ wrapperBlock ], } ); - const originalWrapperBlockCacheKey = - original.cache[ wrapperBlock.clientId ]; - const state = blocks( original, { type: 'REPLACE_BLOCKS', clientIds: [ nestedBlock.clientId ], blocks: [ replacementBlock ], } ); - const newWrapperBlockCacheKey = - state.cache[ wrapperBlock.clientId ]; - - expect( newWrapperBlockCacheKey ).not.toBe( - originalWrapperBlockCacheKey - ); - expect( state.order ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ replacementBlock.clientId ], @@ -840,9 +865,15 @@ describe( 'state', () => { [ replacementBlock.clientId ]: wrapperBlock.clientId, } ); - expect( state.cache ).toEqual( { - [ wrapperBlock.clientId ]: {}, - [ replacementBlock.clientId ]: {}, + expect( state.tree[ wrapperBlock.clientId ].innerBlocks[ 0 ] ).toBe( + state.tree[ replacementBlock.clientId ] + ); + expect( state.tree[ replacementBlock.clientId ] ).toEqual( { + clientId: replacementBlock.clientId, + name: 'core/test-block', + innerBlocks: [], + attributes: {}, + isValid: true, } ); } ); @@ -884,11 +915,8 @@ describe( 'state', () => { '': [ 'chicken' ], chicken: [], } ); - expect( replacedState.cache ).toEqual( { - chicken: {}, - } ); - expect( originalState.cache.chicken ).not.toBe( - replacedState.cache.chicken + expect( originalState.tree.chicken ).not.toBe( + replacedState.tree.chicken ); const nestedBlock = { @@ -963,11 +991,18 @@ describe( 'state', () => { expect( state.attributes.chicken ).toEqual( { content: 'ribs', } ); - - expect( state.cache ).toEqual( { - chicken: {}, + expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( + state.tree.chicken + ); + expect( state.tree.chicken ).toEqual( { + clientId: 'chicken', + name: 'core/test-block', + innerBlocks: [], + attributes: { + content: 'ribs', + }, + isValid: true, } ); - expect( state.cache.chicken ).not.toBe( original.cache.chicken ); } ); it( 'should update the reusable block reference if the temporary id is swapped', () => { @@ -1001,10 +1036,19 @@ describe( 'state', () => { expect( state.attributes.chicken ).toEqual( { ref: 3, } ); - expect( state.cache ).toEqual( { - chicken: {}, + + expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( + state.tree.chicken + ); + expect( state.tree.chicken ).toEqual( { + clientId: 'chicken', + name: 'core/block', + isValid: false, + innerBlocks: [], + attributes: { + ref: 3, + }, } ); - expect( state.cache.chicken ).not.toBe( original.cache.chicken ); } ); it( 'should move the block up', () => { @@ -1031,8 +1075,11 @@ describe( 'state', () => { } ); expect( state.order[ '' ] ).toEqual( [ 'ribs', 'chicken' ] ); - expect( state.cache.ribs ).toBe( original.cache.ribs ); - expect( state.cache.chicken ).toBe( original.cache.chicken ); + expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( state.tree.ribs ); + expect( state.tree[ '' ].innerBlocks[ 1 ] ).toBe( + state.tree.chicken + ); + expect( state.tree.chicken ).toBe( original.tree.chicken ); } ); it( 'should move the nested block up', () => { @@ -1061,14 +1108,15 @@ describe( 'state', () => { [ movedBlock.clientId ]: [], [ siblingBlock.clientId ]: [], } ); - expect( state.cache[ wrapperBlock.clientId ] ).not.toBe( - original.cache[ wrapperBlock.clientId ] + + expect( state.tree[ wrapperBlock.clientId ].innerBlocks[ 0 ] ).toBe( + state.tree[ movedBlock.clientId ] ); - expect( state.cache[ movedBlock.clientId ] ).toBe( - original.cache[ movedBlock.clientId ] + expect( state.tree[ wrapperBlock.clientId ].innerBlocks[ 1 ] ).toBe( + state.tree[ siblingBlock.clientId ] ); - expect( state.cache[ siblingBlock.clientId ] ).toBe( - original.cache[ siblingBlock.clientId ] + expect( state.tree[ movedBlock.clientId ] ).toBe( + original.tree[ movedBlock.clientId ] ); } ); @@ -1351,9 +1399,7 @@ describe( 'state', () => { expect( state.attributes ).toEqual( { ribs: {}, } ); - expect( state.cache ).toEqual( { - ribs: {}, - } ); + expect( state.tree[ '' ].innerBlocks ).toHaveLength( 1 ); } ); it( 'should remove multiple blocks', () => { diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 1d96c678b2b48e..e6f514f8faab9d 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -228,19 +228,19 @@ describe( 'selectors', () => { parents: { 123: '', }, - cache: { - 123: {}, + tree: { + 123: { + clientId: 123, + name: 'core/paragraph', + attributes: {}, + innerBlocks: [], + }, }, controlledInnerBlocks: {}, }, }; - expect( getBlock( state, 123 ) ).toEqual( { - clientId: 123, - name: 'core/paragraph', - attributes: {}, - innerBlocks: [], - } ); + expect( getBlock( state, 123 ) ).toBe( state.blocks.tree[ 123 ] ); } ); it( 'should return null if the block is not present in state', () => { @@ -250,55 +250,19 @@ describe( 'selectors', () => { attributes: {}, order: {}, parents: {}, - cache: {}, - controlledInnerBlocks: {}, - }, - }; - - expect( getBlock( state, 123 ) ).toBe( null ); - } ); - - it( 'should include inner blocks', () => { - const state = { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/paragraph' }, - 456: { clientId: 456, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - 456: {}, - }, - order: { - '': [ 123 ], - 123: [ 456 ], - 456: [], - }, - parents: { - 123: '', - 456: 123, - }, - cache: { - 123: {}, - 456: {}, + tree: { + 123: { + clientId: 123, + name: 'core/paragraph', + attributes: {}, + innerBlocks: [], + }, }, controlledInnerBlocks: {}, }, }; - expect( getBlock( state, 123 ) ).toEqual( { - clientId: 123, - name: 'core/paragraph', - attributes: {}, - innerBlocks: [ - { - clientId: 456, - name: 'core/paragraph', - attributes: {}, - innerBlocks: [], - }, - ], - } ); + expect( getBlock( state, 123 ) ).toBe( null ); } ); } ); @@ -321,7 +285,23 @@ describe( 'selectors', () => { 123: '', 23: '', }, - cache: { + tree: { + '': { + innerBlocks: [ + { + clientId: 123, + name: 'core/paragraph', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 23, + name: 'core/heading', + attributes: {}, + innerBlocks: [], + }, + ], + }, 123: {}, 23: {}, }, @@ -329,92 +309,9 @@ describe( 'selectors', () => { }, }; - expect( getBlocks( state ) ).toEqual( [ - { - clientId: 123, - name: 'core/paragraph', - attributes: {}, - innerBlocks: [], - }, - { - clientId: 23, - name: 'core/heading', - attributes: {}, - innerBlocks: [], - }, - ] ); - } ); - it( 'only returns a new value if the cache key of a direct child changes', () => { - const cacheRef = {}; - const state = { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - }, - attributes: { - 23: {}, - }, - order: { - '': [ 23 ], - }, - parents: { - 23: '', - }, - cache: { - 23: cacheRef, - }, - controlledInnerBlocks: {}, - }, - }; - const oldBlocks = getBlocks( state ); - - const newStateSameCache = { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - }, - attributes: { - 23: {}, - }, - order: { - '': [ 23 ], - }, - parents: { - 23: '', - }, - cache: { - 23: cacheRef, - }, - controlledInnerBlocks: {}, - }, - }; - // Makes sure blocks are referentially equal if the cache key stays the same. - const newBlocks = getBlocks( newStateSameCache ); - expect( oldBlocks ).toBe( newBlocks ); - - const newStateNewCache = { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - }, - attributes: { - 23: {}, - }, - order: { - '': [ 23 ], - }, - parents: { - 23: '', - }, - cache: { - 23: {}, - }, - controlledInnerBlocks: {}, - }, - }; - // Blocks are referentially different if the cache key changes. - const newBlocksNewCache = getBlocks( newStateNewCache ); - expect( oldBlocks ).not.toBe( newBlocksNewCache ); + expect( getBlocks( state ) ).toBe( + state.blocks.tree[ '' ].innerBlocks + ); } ); } ); @@ -963,6 +860,14 @@ describe( 'selectors', () => { 23: '', 123: '', }, + tree: { + 23: { + clientId: 23, + name: 'core/heading', + attributes: {}, + innerBlocks: [], + }, + }, }, selection: { selectionStart: {}, @@ -993,6 +898,14 @@ describe( 'selectors', () => { 123: '', 23: '', }, + tree: { + 23: { + clientId: 23, + name: 'core/heading', + attributes: {}, + innerBlocks: [], + }, + }, }, selection: { selectionStart: { clientId: 23 }, @@ -1023,8 +936,13 @@ describe( 'selectors', () => { 123: '', 23: '', }, - cache: { - 23: {}, + tree: { + 23: { + clientId: 23, + name: 'core/heading', + attributes: {}, + innerBlocks: [], + }, }, controlledInnerBlocks: {}, }, @@ -1034,12 +952,9 @@ describe( 'selectors', () => { }, }; - expect( getSelectedBlock( state ) ).toEqual( { - clientId: 23, - name: 'core/heading', - attributes: {}, - innerBlocks: [], - } ); + expect( getSelectedBlock( state ) ).toEqual( + getBlock( state, 23 ) + ); } ); } ); @@ -2560,7 +2475,11 @@ describe( 'selectors', () => { attributes: {}, order: {}, parents: {}, - cache: {}, + tree: { + '': { + innerBlocks: [], + }, + }, }, settings: { __experimentalReusableBlocks: [ @@ -2636,9 +2555,19 @@ describe( 'selectors', () => { block3: '', block4: '', }, - cache: { - block3: {}, - block4: {}, + tree: { + block3: { + clientId: 'block3', + name: 'core/test-block-a', + attributes: {}, + innerBlocks: [], + }, + block4: { + clientId: 'block4', + name: 'core/test-block-a', + attributes: {}, + innerBlocks: [], + }, }, controlledInnerBlocks: {}, }, @@ -2726,8 +2655,13 @@ describe( 'selectors', () => { order: { '': [ 'block1' ], }, - cache: { - block1: {}, + tree: { + block1: { + clientId: 'block1', + name: 'core/test-block-b', + attributes: {}, + innerBlocks: [], + }, }, controlledInnerBlocks: {}, }, @@ -2929,8 +2863,13 @@ describe( 'selectors', () => { order: { '': [ 'block1' ], }, - cache: { - block1: {}, + tree: { + block1: { + clientId: 'block1', + name: 'core/with-tranforms-c', + attributes: {}, + innerBlocks: [], + }, }, controlledInnerBlocks: {}, }, diff --git a/packages/edit-site/src/components/template-part-converter/convert-to-regular.js b/packages/edit-site/src/components/template-part-converter/convert-to-regular.js index cfc43db26a657a..6407f8a22b0717 100644 --- a/packages/edit-site/src/components/template-part-converter/convert-to-regular.js +++ b/packages/edit-site/src/components/template-part-converter/convert-to-regular.js @@ -10,13 +10,7 @@ import { MenuItem } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; export default function ConvertToRegularBlocks( { clientId } ) { - const { innerBlocks } = useSelect( - ( select ) => - select( blockEditorStore ).__unstableGetBlockWithBlockTree( - clientId - ), - [ clientId ] - ); + const { getBlocks } = useSelect( blockEditorStore ); const { replaceBlocks } = useDispatch( blockEditorStore ); return ( @@ -24,7 +18,7 @@ export default function ConvertToRegularBlocks( { clientId } ) { { ( { onClose } ) => ( { - replaceBlocks( clientId, innerBlocks ); + replaceBlocks( clientId, getBlocks( clientId ) ); onClose(); } } > diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 00d3e6527a71c4..d3e185e051e114 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1394,13 +1394,6 @@ export const getBlock = getBlockEditorSelector( 'getBlock' ); */ export const getBlocks = getBlockEditorSelector( 'getBlocks' ); -/** - * @see __unstableGetBlockWithoutInnerBlocks in core/block-editor store. - */ -export const __unstableGetBlockWithoutInnerBlocks = getBlockEditorSelector( - '__unstableGetBlockWithoutInnerBlocks' -); - /** * @see getClientIdsOfDescendants in core/block-editor store. */ From 8d1ea0a3ad5440c64e8fae273e3d8125a3f61500 Mon Sep 17 00:00:00 2001 From: Alex Stine Date: Thu, 26 Aug 2021 05:11:28 -0400 Subject: [PATCH 046/214] Accessibility improvement for font weight screen reader description (#34312) * Accessibility improvement for font weight/style description text. * Fix linter error. * Fix linter error. --- .../font-appearance-control/index.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/block-editor/src/components/font-appearance-control/index.js b/packages/block-editor/src/components/font-appearance-control/index.js index 224f9361d32f25..80b3b24357d660 100644 --- a/packages/block-editor/src/components/font-appearance-control/index.js +++ b/packages/block-editor/src/components/font-appearance-control/index.js @@ -163,12 +163,38 @@ export default function FontAppearanceControl( props ) { return __( 'Appearance' ); }; + // Adjusts screen reader description based on styles or weights. + const getDescribedBy = () => { + if ( ! hasFontStyles ) { + return sprintf( + // translators: %s: Currently selected font weight. + __( 'Currently selected font weight: %s' ), + currentSelection.name + ); + } + + if ( ! hasFontWeights ) { + return sprintf( + // translators: %s: Currently selected font style. + __( 'Currently selected font style: %s' ), + currentSelection.name + ); + } + + return sprintf( + // translators: %s: Currently selected font appearance. + __( 'Currently selected font appearance: %s' ), + currentSelection.name + ); + }; + return (
{ hasStylesOrWeights && ( From 96e4fcbaac987aca47985d86fa4707bc09d10083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Thu, 26 Aug 2021 12:56:09 +0200 Subject: [PATCH 047/214] Jest Preset: Restore the default setting for the `verbose` option (#34327) --- packages/jest-preset-default/CHANGELOG.md | 4 ++++ packages/jest-preset-default/jest-preset.js | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/jest-preset-default/CHANGELOG.md b/packages/jest-preset-default/CHANGELOG.md index 7342d85b69610d..6e18c4dd854ae0 100644 --- a/packages/jest-preset-default/CHANGELOG.md +++ b/packages/jest-preset-default/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fix + +- Restore the default setting for the `verbose` option. In effect, each test won't get reported during the run ([#34327](https://github.com/WordPress/gutenberg/pull/34327)). + ## 7.0.0 (2021-01-21) ### Breaking Changes diff --git a/packages/jest-preset-default/jest-preset.js b/packages/jest-preset-default/jest-preset.js index 252ac61a9c5df0..5dc0277f2d88e2 100644 --- a/packages/jest-preset-default/jest-preset.js +++ b/packages/jest-preset-default/jest-preset.js @@ -26,5 +26,4 @@ module.exports = { transform: { '^.+\\.[jt]sx?$': require.resolve( 'babel-jest' ), }, - verbose: true, }; From 640331cd0ac85eabc5ece96acc9a0494ed17b83d Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 26 Aug 2021 12:35:44 +0100 Subject: [PATCH 048/214] Code cleanup to the getBlock refactoring (#34326) --- docs/reference-guides/data/data-core-block-editor.md | 4 ++-- .../src/components/block-list/block-list-item.native.js | 2 +- packages/block-editor/src/store/actions.js | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index 2f5b5193452558..9f07302f001a83 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -1215,12 +1215,12 @@ _Parameters_ ### receiveBlocks +> **Deprecated** + Returns an action object used in signalling that blocks have been received. Unlike resetBlocks, these should be appended to the existing known set, not replacing. -Todo: This should be deprecated - _Parameters_ - _blocks_ `Object[]`: Array of block objects. diff --git a/packages/block-editor/src/components/block-list/block-list-item.native.js b/packages/block-editor/src/components/block-list/block-list-item.native.js index 0abe3c213d4a90..857f0ed66ff51a 100644 --- a/packages/block-editor/src/components/block-list/block-list-item.native.js +++ b/packages/block-editor/src/components/block-list/block-list-item.native.js @@ -225,7 +225,7 @@ export default compose( [ const isReadOnly = getSettings().readOnly; - const { attributes, name } = getBlock( clientId ); + const { attributes, name } = getBlock( clientId ) || {}; const { align } = attributes || {}; const parents = getBlockParents( clientId, true ); const hasParents = !! parents.length; diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 817953f3aef242..cd09e91dfbef9c 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -151,13 +151,18 @@ export function resetSelection( * Unlike resetBlocks, these should be appended to the existing known set, not * replacing. * - * Todo: This should be deprecated + * @deprecated * * @param {Object[]} blocks Array of block objects. * * @return {Object} Action object. */ export function receiveBlocks( blocks ) { + deprecated( 'wp.data.dispatch( "core/block-editor" ).receiveBlocks', { + since: '5.9', + alternative: 'resetBlocks or insertBlocks', + } ); + return { type: 'RECEIVE_BLOCKS', blocks, From bc42199951c32769deb4b356eb768914b666aa77 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Thu, 26 Aug 2021 15:09:49 +0200 Subject: [PATCH 049/214] Fix gray W menu color. (#34318) --- .../src/components/header/fullscreen-mode-close/style.scss | 4 ++-- .../navigation-sidebar/navigation-toggle/style.scss | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss b/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss index 1cee0b961908f2..72f14c823e168c 100644 --- a/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss +++ b/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss @@ -11,7 +11,7 @@ align-items: center; align-self: stretch; border: none; - background: #23282e; // WP-admin gray. + background: $gray-900; color: $white; border-radius: 0; height: $header-height + $border-width; @@ -38,7 +38,7 @@ bottom: 9px; left: 9px; border-radius: $radius-block-ui + $border-width + $border-width; - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) #23282e; + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) $gray-900; } // Hover color. diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/style.scss b/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/style.scss index d3ab0f7ac3d5b1..d78d3d4e6f6681 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/style.scss +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/style.scss @@ -17,9 +17,10 @@ background: $gray-900; border-radius: 0; color: $white; - height: $header-height; + height: $header-height + $border-width; width: $header-height; z-index: 1; + margin-bottom: - $border-width; &.has-icon { min-width: $header-height; @@ -44,7 +45,7 @@ bottom: 9px; left: 9px; border-radius: $radius-block-ui + $border-width + $border-width; - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) #23282e; + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) $gray-900; } // Hover color. From 2f963dd8d0039d70861d2e05760c1f25fa228f95 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Thu, 26 Aug 2021 15:47:02 +0200 Subject: [PATCH 050/214] Fix subheadings from wrapping. (#34319) --- packages/components/src/menu-group/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/src/menu-group/style.scss b/packages/components/src/menu-group/style.scss index 202bd74a469c1e..d9412c504940b3 100644 --- a/packages/components/src/menu-group/style.scss +++ b/packages/components/src/menu-group/style.scss @@ -18,4 +18,5 @@ text-transform: uppercase; font-size: 11px; font-weight: 500; + white-space: nowrap; } From c3d2b8ba48691ecf4d0a43eab887c5df581a3c9f Mon Sep 17 00:00:00 2001 From: Janw Oostendorp Date: Thu, 26 Aug 2021 16:37:17 +0200 Subject: [PATCH 051/214] Added janw-me to the Codeowners for the PHP FSE folder. (#32990) * Added janw-me to the Codeowners for the PHP FSE folder. --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 85d23fb60f268c..b22eec7c45d400 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -127,6 +127,7 @@ /lib/theme.json @timothybjabocs @spacedmonkey @nosolosw /lib/class-wp-theme-json-gutenberg.php @timothybjabocs @spacedmonkey @nosolosw /lib/class-wp-theme-json-resolver-gutenberg.php @timothybjabocs @spacedmonkey @nosolosw +/lib/full-site-editing @janw-me /phpunit/class-wp-theme-json-test.php @nosolosw # Web App From be7954e0fa98664613e9d8fda7478ad21023b804 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Thu, 26 Aug 2021 10:14:21 -0500 Subject: [PATCH 052/214] Fix Column bottom sheet Android close button (#34332) * Fix Column bottom sheet Android close button Apply required margin and padding to properly align Android close button in the Column block bottom sheet. * Update changelog --- .../src/components/block-variation-picker/style.native.scss | 2 ++ packages/react-native-editor/CHANGELOG.md | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/block-editor/src/components/block-variation-picker/style.native.scss b/packages/block-editor/src/components/block-variation-picker/style.native.scss index c8bb66b7a714a0..20b1297a593b15 100644 --- a/packages/block-editor/src/components/block-variation-picker/style.native.scss +++ b/packages/block-editor/src/components/block-variation-picker/style.native.scss @@ -27,4 +27,6 @@ .closeIcon { color: $gray; + margin-left: $grid-unit-20; + padding: $grid-unit-20; } diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index e8fd2e35beeae0..b7bb379c9b9a45 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -10,6 +10,7 @@ For each user feature we should also add a importance categorization label to i --> ## Unreleased +- [*] Column block: Fix Android close button alignment. [#34332] ## 1.60.0 - [**] Embed block: Add "Resize for smaller devices" setting. [#33654] From 0b9114fa9c9372c162ee65762e97bb85a4241073 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 26 Aug 2021 11:30:09 -0400 Subject: [PATCH 053/214] RNMobile: Fix links, images, and formatting in documentation (#34300) * RNMobile: Fix doc links, images and formatting * Doc links were broken because they used relative paths to files outside of the docs folder * An image was broken because it didn't use a direct link but instead used a relative link * Code snippets needed to be indented properly * Add language specifier to native doc code snippets --- .../code/getting-started-native-mobile.md | 34 ++++++---- .../native-mobile-integration-test-guide.md | 68 +++++++++---------- docs/contributors/code/native-mobile.md | 2 +- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/docs/contributors/code/getting-started-native-mobile.md b/docs/contributors/code/getting-started-native-mobile.md index a3dbeedcd98a31..dbdeba751aa503 100644 --- a/docs/contributors/code/getting-started-native-mobile.md +++ b/docs/contributors/code/getting-started-native-mobile.md @@ -9,15 +9,15 @@ For a developer experience closer to the one the project maintainers current hav - git - [nvm](https://github.com/creationix/nvm) - Node.js and npm (use nvm to install them) -- [AndroidStudio](https://developer.android.com/studio/) to be able to compile the Android version of the app +- [Android Studio](https://developer.android.com/studio/) to be able to compile the Android version of the app - [Xcode](https://developer.apple.com/xcode/) to be able to compile the iOS app -- CocoaPods(`sudo gem install cocoapods`) needed to fetch React and third-party dependencies. +- CocoaPods (`sudo gem install cocoapods`) needed to fetch React and third-party dependencies. Note that the OS platform used by the maintainers is macOS but the tools and setup should be usable in other platforms too. ## Clone the project -``` +```sh git clone https://github.com/WordPress/gutenberg.git ``` @@ -25,14 +25,14 @@ git clone https://github.com/WordPress/gutenberg.git Note that the commands described here should be run in the top-level directory of the cloned project. Before running the demo app, you need to download and install the project dependencies. This is done via the following command: -``` +```sh nvm install npm ci ``` ## Run -``` +```sh npm run native start:reset ``` @@ -40,7 +40,7 @@ Runs the packager (Metro) in development mode. The packager stays running to ser With the packager running, open another terminal window and use the following command to compile and run the Android app: -``` +```sh npm run native android ``` @@ -48,7 +48,7 @@ The app should now open in a connected device or a running emulator and fetch th To compile and run the iOS variant of the app using the _default_ simulator device, use: -``` +```sh npm run native ios ``` @@ -58,13 +58,13 @@ which will attempt to open your app in the iOS Simulator if you're on a Mac and To compile and run the app using a different device simulator, use the following, noting the double sets of `--` to pass the simulator option down to the `react-native` CLI. -``` +```sh npm run native ios -- -- --simulator="DEVICE_NAME" ``` For example, if you'd like to run in an iPhone Xs Max, try: -``` +```sh npm run native ios -- -- --simulator="iPhone Xs Max" ``` @@ -86,7 +86,7 @@ One of the extensions we are using is the [React Native Tools](https://marketpla Use the following command to run the test suite: -``` +```sh npm run native test ``` @@ -94,7 +94,7 @@ It will run the [jest](https://github.com/facebook/jest) test runner on your tes To run the tests with debugger support, start it with the following CLI command: -``` +```sh npm run native test:debug ``` @@ -114,15 +114,21 @@ This repository uses Appium to run UI tests. The tests live in `__device-tests__ Then, to run the UI tests on iOS: -`npm run native test:e2e:ios:local` +```sh +npm run native test:e2e:ios:local +``` and for Android: -`npm run native test:e2e:android:local` +```sh +npm run native test:e2e:android:local +``` To run a single test instead of the entire suite, use `npm run native device-tests:local`. Here's an example that runs only `gutenberg-editor-gallery.test.js`: -`npm run native test:e2e:android:local gutenberg-editor-gallery.test.js` +```sh +npm run native test:e2e:android:local gutenberg-editor-gallery.test.js +``` Note: You might experience problems that seem to be related to the tests starting the Appium server, e.g. errors that say `Connection Refused`, `Connection Reset` or `The requested environment is not available`. For now, you can manually start the Appium server via [appium desktop](https://github.com/appium/appium-desktop) or the CLI, then change the port number in the tests while (optionally) commenting out related code in the `beforeAll` and `afterAll` block. diff --git a/docs/contributors/code/native-mobile-integration-test-guide.md b/docs/contributors/code/native-mobile-integration-test-guide.md index 46bcd271094b2d..14cb479dae1948 100644 --- a/docs/contributors/code/native-mobile-integration-test-guide.md +++ b/docs/contributors/code/native-mobile-integration-test-guide.md @@ -27,7 +27,7 @@ This part usually is covered by using the Jest callbacks `beforeAll` and `before Here is an example of a common pattern if we expect all core blocks to be available: -``` +```js beforeAll( () => { // Register all core blocks registerCoreBlocks(); @@ -42,7 +42,7 @@ Before introducing the testing logic, we have to render the components that we w Here is an example of rendering the Cover block (extracted from [this code](https://github.com/WordPress/gutenberg/blob/86cd187873984f80ddeeec3e82454b486dd1860f/packages/block-library/src/cover/test/edit.native.js#L82-L91)): -``` +```js // This import points to the index file of the block import { metadata, settings, name } from '../index'; @@ -83,7 +83,7 @@ const { getByText, findByText } = render( Here is an example of rendering the Buttons block (extracted from [this code](https://github.com/WordPress/gutenberg/blob/9201906891a68ca305daf7f8b6cd006e2b26291e/packages/block-library/src/buttons/test/edit.native.js#L32-L39)): -``` +```js const initialHtml = `
@@ -106,15 +106,15 @@ When querying we should follow this priority order: Here are some examples: -``` +```js const mediaLibraryButton = getByText( 'WordPress Media Library' ); ``` -``` +```js const missingBlock = getByA11yLabel( /Unsupported Block\. Row 1/ ); ``` -``` +```js const radiusSlider = getByTestId( 'Slider Border Radius' ); ``` @@ -126,19 +126,19 @@ After rendering the components or firing an event, side effects might happen due Here are some examples: -``` +```js const mediaLibraryButton = await waitFor( () => getByText( 'WordPress Media Library' ) ); ``` -``` +```js const missingBlock = await waitFor( () => getByA11yLabel( /Unsupported Block\. Row 1/ ) ); ``` -``` +```js const radiusSlider = await waitFor( () => getByTestId( 'Slider Border Radius' ) ); @@ -152,13 +152,11 @@ NOTE: The `react-native-testing-library` package provides the `query*` and `find It’s also possible to query elements contained in other elements via the `within` function, here is an example: -``` +```js const missingBlock = await waitFor( () => getByA11yLabel( /Unsupported Block\. Row 1/ ) ); -const translatedTableTitle = within( missingBlock ).getByText( - 'Tabla' -); +const translatedTableTitle = within( missingBlock ).getByText( 'Tabla' ); ``` ## Fire events @@ -169,7 +167,7 @@ Here is an example of a press event: **Press event:** -``` +```js fireEvent.press( settingsButton ); ``` @@ -177,7 +175,7 @@ We can also trigger any type of event, including custom events, in the following **Custom event – onValueChange:** -``` +```js fireEvent( heightSlider, 'valueChange', '50' ); ``` @@ -187,18 +185,16 @@ After querying elements and firing events, we have to verify that the logic work Here is an example: -``` -const translatedTableTitle = within( missingBlock ).getByText( - 'Tabla' -); +```js +const translatedTableTitle = within( missingBlock ).getByText( 'Tabla' ); expect( translatedTableTitle ).toBeDefined(); ``` Additionally when rendering the entire editor, we can also verify if the HTML output is what we expect: -``` +```js expect( getEditorHtml() ).toBe( -'\n\n' + '\n\n' ); ``` @@ -206,7 +202,7 @@ expect( getEditorHtml() ).toBe( And finally, we have to clean up any potential modifications we’ve made that could affect the following tests. Here is an example of a typical cleanup after registering blocks that implies unregistering all blocks: -``` +```js afterAll( () => { // Clean up registered blocks getBlockTypes().forEach( ( block ) => { @@ -221,9 +217,9 @@ afterAll( () => { A common way to query a block is by its accessibility label, here is an example: -``` +```js const spacerBlock = await waitFor( () => -getByA11yLabel( /Spacer Block\. Row 1/ ) + getByA11yLabel( /Spacer Block\. Row 1/ ) ); ``` @@ -233,7 +229,7 @@ For further information about the accessibility label of a block, you can check Here is an example of how to insert a Paragraph block: -``` +```js // Open the inserter menu fireEvent.press( await waitFor( () => getByA11yLabel( 'Add block' ) ) ); @@ -255,12 +251,10 @@ fireEvent.press( await waitFor( () => getByText( `Paragraph` ) ) ); The block settings can be accessed by tapping the "Open Settings" button after selecting the block, here is an example: -``` +```js fireEvent.press( block ); -const settingsButton = await waitFor( () => - getByA11yLabel( 'Open Settings' ) -); +const settingsButton = await waitFor( () => getByA11yLabel( 'Open Settings' ) ); fireEvent.press( settingsButton ); ``` @@ -268,10 +262,10 @@ fireEvent.press( settingsButton ); When using the scoped component approach, we need first to render the `SlotFillProvider` and the `BottomSheetSettings` (note that we’re passing the `isVisible` prop to force the bottom sheet to be displayed) along with the block: -``` +```js - - + + ``` @@ -285,7 +279,7 @@ The `FlatList` component renders its items depending on the scroll position, the Here is an example of the FlatList used for rendering the block list in the inserter menu: -``` +```js const blockList = getByTestId( 'InserterUI-Blocks' ); // onScroll event used to force the FlatList to render all items fireEvent.scroll( blockList, { @@ -301,7 +295,7 @@ fireEvent.scroll( blockList, { Sliders found in bottom sheets should be queried using their `testID`: -``` +```js const radiusSlider = await waitFor( () => getByTestId( 'Slider Border Radius' ) ); @@ -314,7 +308,7 @@ Note that a slider’s `testID` is "Slider " + label. So for a slider with a lab One caveat when adding blocks is that if they contain inner blocks, these inner blocks are not rendered. The following example shows how we can make a Buttons block render its inner Button blocks (assumes we’ve already obtained a reference to the Buttons block as `buttonsBlock`): -``` +```js const innerBlockListWrapper = await waitFor( () => within( buttonsBlock ).getByTestId( 'block-list-wrapper' ) ); @@ -338,7 +332,7 @@ fireEvent.press( buttonInnerBlock ); If you have trouble locating an element’s identifier, you may wish to use Xcode’s Accessibility Inspector. Most identifiers are cross-platform, so even though the tests are run on Android by default, the Accessibility Inspector can be used to find the right identifier. -Screenshot of the Xcode Accessibility Inspector app. The screenshot shows how to choose the correct target in the device dropdown, enable target mode, and locate accessibility labels after tapping on screen elements +Screenshot of the Xcode Accessibility Inspector app. The screenshot shows how to choose the correct target in the device dropdown, enable target mode, and locate accessibility labels after tapping on screen elements ## Common pitfalls and caveats @@ -362,7 +356,7 @@ By default, all tests run in Jest use the Android platform, so in case we need t In case we only need to test logic controlled by the Platform object, we can mock the module with the following code (in this case it’s changing the platform to iOS): -``` +```js jest.mock( 'Platform', () => { const Platform = jest.requireActual( 'Platform' ); Platform.OS = 'ios'; diff --git a/docs/contributors/code/native-mobile.md b/docs/contributors/code/native-mobile.md index d216720eda4c35..2db44df33fef38 100644 --- a/docs/contributors/code/native-mobile.md +++ b/docs/contributors/code/native-mobile.md @@ -21,7 +21,7 @@ Also, the mobile client is packaged and released via the [official WordPress app If you encounter a failed Android/iOS test on your pull request, we recommend the following steps: 1. Re-running the failed GitHub Action job ([guide for how to re-run](https://docs.github.com/en/actions/configuring-and-managing-workflows/managing-a-workflow-run#viewing-your-workflow-history)) - This should fix failed tests the majority of the time. Cases where you need to re-run tests for a pass should go down in the near future as flakiness in tests is actively being worked on. See the following GitHub issue for updated info on known failures: https://github.com/WordPress/gutenberg/issues/23949 -2. You can check if the test is failing locally by following the steps to run the E2E test on your machine from the [mobile getting started guide](/docs/contributors/code/getting-started-with-code-contribution-native-mobile.md#ui-tests), with even more relevant info in the [relevant directory README.md](https://github.com/WordPress/gutenberg/tree/HEAD/packages/react-native-editor/__device-tests__#running-the-tests-locally) +2. You can check if the test is failing locally by following the steps to run the E2E test on your machine from the [mobile getting started guide](/docs/contributors/code/getting-started-native-mobile.md#ui-tests), with even more relevant info in the [relevant directory README.md](https://github.com/WordPress/gutenberg/tree/HEAD/packages/react-native-editor/__device-tests__#running-the-tests-locally) 3. In addition to reading the logs from the E2E test, you can download a video recording from the Artifacts section of the GitHub job that may have additional useful information. 4. Check if any changes in your PR would require corresponding changes to `.native.js` versions of files. 5. Lastly, if you're stuck on a failing mobile test, feel free to reach out to contributors on Slack in the #mobile or #core-editor chats in the WordPress Core Slack, [free to join](https://make.wordpress.org/chat/). From a31cb9551c58b62d651c72999df2776641d81200 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Thu, 26 Aug 2021 17:40:01 +0200 Subject: [PATCH 054/214] Try: Vertical heading levels menu (#32926) --- .../block-library/src/heading/editor.scss | 14 +--- .../src/heading/heading-level-dropdown.js | 84 ++++++------------- .../components/src/dropdown-menu/index.js | 2 + .../components/src/dropdown-menu/style.scss | 5 ++ 4 files changed, 34 insertions(+), 71 deletions(-) diff --git a/packages/block-library/src/heading/editor.scss b/packages/block-library/src/heading/editor.scss index a1af2ba3702442..f70daaf4d3e652 100644 --- a/packages/block-library/src/heading/editor.scss +++ b/packages/block-library/src/heading/editor.scss @@ -1,20 +1,8 @@ // Remove padding in heading level control popover since the toolbar buttons already have padding. .block-library-heading-level-dropdown .components-popover__content { - // TODO: Find a less hardcoded way of doing this. `max-content` works on - // Chromium, but it results in a scrollbar on Safari, and isn't supported - // at all in IE11. - min-width: 230px; + min-width: auto; > div { padding: 0; } } - -// The dropdown already has a border, so we can remove the one on the heading -// level toolbar. -.block-library-heading-level-toolbar { - border: none; - .components-toolbar-group { - flex-wrap: nowrap; - } -} diff --git a/packages/block-library/src/heading/heading-level-dropdown.js b/packages/block-library/src/heading/heading-level-dropdown.js index 84d00181b71297..592787b5fa9ddc 100644 --- a/packages/block-library/src/heading/heading-level-dropdown.js +++ b/packages/block-library/src/heading/heading-level-dropdown.js @@ -1,14 +1,8 @@ /** * WordPress dependencies */ -import { - Dropdown, - Toolbar, - ToolbarButton, - ToolbarGroup, -} from '@wordpress/components'; +import { ToolbarDropdownMenu } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { DOWN } from '@wordpress/keycodes'; /** * Internal dependencies @@ -19,7 +13,6 @@ const HEADING_LEVELS = [ 1, 2, 3, 4, 5, 6 ]; const POPOVER_PROPS = { className: 'block-library-heading-level-dropdown', - isAlternate: true, }; /** @typedef {import('@wordpress/element').WPComponent} WPComponent */ @@ -43,58 +36,33 @@ const POPOVER_PROPS = { */ export default function HeadingLevelDropdown( { selectedLevel, onChange } ) { return ( - { - const openOnArrowDown = ( event ) => { - if ( ! isOpen && event.keyCode === DOWN ) { - event.preventDefault(); - onToggle(); - } - }; + icon={ } + label={ __( 'Change heading level' ) } + controls={ HEADING_LEVELS.map( ( targetLevel ) => { + { + const isActive = targetLevel === selectedLevel; - return ( - } - label={ __( 'Change heading level' ) } - onClick={ onToggle } - onKeyDown={ openOnArrowDown } - showTooltip - /> - ); - } } - renderContent={ () => ( - - { - const isActive = targetLevel === selectedLevel; - return { - icon: ( - - ), - title: sprintf( - // translators: %s: heading level e.g: "1", "2", "3" - __( 'Heading %d' ), - targetLevel - ), - isActive, - onClick() { - onChange( targetLevel ); - }, - }; - } ) } - /> - - ) } + return { + icon: ( + + ), + label: sprintf( + // translators: %s: heading level e.g: "1", "2", "3" + __( 'Heading %d' ), + targetLevel + ), + isActive, + onClick() { + onChange( targetLevel ); + }, + }; + } + } ) } /> ); } diff --git a/packages/components/src/dropdown-menu/index.js b/packages/components/src/dropdown-menu/index.js index 9f734f31a317c0..f2ae27cd91e7d7 100644 --- a/packages/components/src/dropdown-menu/index.js +++ b/packages/components/src/dropdown-menu/index.js @@ -173,9 +173,11 @@ function DropdownMenu( { indexOfSet > 0 && indexOfControl === 0, 'is-active': control.isActive, + 'is-icon-only': ! control.title, } ) } icon={ control.icon } + label={ control.label } aria-checked={ control.role === 'menuitemcheckbox' || control.role === 'menuitemradio' diff --git a/packages/components/src/dropdown-menu/style.scss b/packages/components/src/dropdown-menu/style.scss index 958ac9b5a3caeb..60e1a91270294f 100644 --- a/packages/components/src/dropdown-menu/style.scss +++ b/packages/components/src/dropdown-menu/style.scss @@ -44,6 +44,11 @@ width: $button-size-small; height: $button-size-small; } + + // If menu items are icon-only, make them stretch only to the icon size. + &.is-icon-only { + width: auto; + } } .components-menu-item__button, From 5fd2c0919ed8644bb61e3dc7cbf19f859d1ae7f6 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 26 Aug 2021 17:05:56 -0400 Subject: [PATCH 055/214] Update userAgent to be string instead of array (#34308) This change fixes issues that could arise with third-party code in Gutenberg Mobile which expects the userAgent to be a string. --- packages/react-native-editor/src/globals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-editor/src/globals.js b/packages/react-native-editor/src/globals.js index 4e66ecb2c63939..cea706b35c1377 100644 --- a/packages/react-native-editor/src/globals.js +++ b/packages/react-native-editor/src/globals.js @@ -59,7 +59,7 @@ if ( ! global.window.matchMedia ) { } ); } -global.window.navigator.userAgent = []; +global.window.navigator.userAgent = global.window.navigator.userAgent ?? ''; // Leverages existing console polyfill from react-native global.nativeLoggingHook = nativeLoggingHook; From d72c07625ad9721e3fd6df0d2409ea3b61e42b2a Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Thu, 26 Aug 2021 23:59:59 +0200 Subject: [PATCH 056/214] Mobile Release v1.60.1 (#34294) * Release script: Update react-native-editor version to 1.60.0 * Release script: Update with changes from 'npm run core preios' * Update changelog * Release script: Update react-native-editor version to 1.60.1 * Release script: Update with changes from 'npm run core preios' * RNmobile: Fix the cancel button on Columns Block (#34249) * Mobile - Update changelog * Fix Column bottom sheet Android close button (#34332) * Fix Column bottom sheet Android close button Apply required margin and padding to properly align Android close button in the Column block bottom sheet. * Update changelog * Mobile - Update changelog Co-authored-by: Enej Bajgoric Co-authored-by: David Calhoun <438664+dcalhoun@users.noreply.github.com> --- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/CHANGELOG.md | 3 +++ packages/react-native-editor/ios/Podfile.lock | 8 ++++---- packages/react-native-editor/package.json | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index dbfee6b32a2da1..f188e0722eac0d 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.60.0", + "version": "1.60.1", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index 04a797846903c4..e0ffa44432a45f 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.60.0", + "version": "1.60.1", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index b7bb379c9b9a45..663f41c835aede 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -10,6 +10,9 @@ For each user feature we should also add a importance categorization label to i --> ## Unreleased + +## 1.60.1 +- [*] RNmobile: Fix the cancel button on Block Variation Picker / Columns Block. [#34249] - [*] Column block: Fix Android close button alignment. [#34332] ## 1.60.0 diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index a33a78ec390365..6a01df08345d4a 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -12,7 +12,7 @@ PODS: - React-jsi (= 0.64.0) - ReactCommon/turbomodule/core (= 0.64.0) - glog (0.3.5) - - Gutenberg (1.60.0): + - Gutenberg (1.60.1): - React-Core (= 0.64.0) - React-CoreModules (= 0.64.0) - React-RCTImage (= 0.64.0) @@ -303,7 +303,7 @@ PODS: - React-Core - RNSVG (9.13.7-wp): - React-Core - - RNTAztecView (1.60.0): + - RNTAztecView (1.60.1): - React-Core - WordPress-Aztec-iOS (~> 1.19.4) - WordPress-Aztec-iOS (1.19.4) @@ -459,7 +459,7 @@ SPEC CHECKSUMS: FBLazyVector: 49cbe4b43e445b06bf29199b6ad2057649e4c8f5 FBReactNativeSpec: 80e9cf1155002ee4720084d8813326d913815e2f glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62 - Gutenberg: d18ce5f2f99f78cd9c758d89d37c82a91be5cadd + Gutenberg: bc75d848519ff99520b0109a3d7f9c6f82caeae7 RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c RCTRequired: 2f8cb5b7533219bf4218a045f92768129cf7050a RCTTypeSafety: 512728b73549e72ad7330b92f3d42936f2a4de5b @@ -496,7 +496,7 @@ SPEC CHECKSUMS: RNReanimated: ca6105fdc2739ea1b3a7a5350b6490d8160143bc RNScreens: eb4e23256e7f2a5a1af87ea24dfeb49aea0ef310 RNSVG: 1b6dcbec5884b6dbe256bf8c38eeeab0acf05926 - RNTAztecView: 9ad0125ab87d59989d5279cdfdc2863e068059cb + RNTAztecView: 9611e25a2e52d206e7979081294b7c40c20d1bd5 WordPress-Aztec-iOS: 870c93297849072aadfc2223e284094e73023e82 Yoga: 8c8436d4171c87504c648ae23b1d81242bdf3bbf diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 80aadd8c241ca8..91e4b9246ad81e 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-editor", - "version": "1.60.0", + "version": "1.60.1", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From b18b086b6823de5255b5bc2058974e0171ac3a34 Mon Sep 17 00:00:00 2001 From: Jason Johnston Date: Thu, 26 Aug 2021 18:09:29 -0400 Subject: [PATCH 057/214] Only handle keyboard hiding on Android (#34336) Co-authored-by: jhnstn --- packages/components/src/search-control/index.native.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/components/src/search-control/index.native.js b/packages/components/src/search-control/index.native.js index 6797c0c6a0bd95..2bf19b8a42b54b 100644 --- a/packages/components/src/search-control/index.native.js +++ b/packages/components/src/search-control/index.native.js @@ -121,7 +121,9 @@ function SearchControl( { const keyboardHideSubscription = Keyboard.addListener( 'keyboardDidHide', () => { - onCancel(); + if ( ! isIOS ) { + onCancel(); + } } ); return () => { From d75d953285df3d68bed216b0d58413f5591601f4 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 27 Aug 2021 10:29:46 +1000 Subject: [PATCH 058/214] Add getFilename method to the url package (#34313) * Add getFilename method to url package * Update File block transform to use getFilename * Update Image and Video blocks to use getFilename method --- packages/block-library/src/file/transforms.js | 7 ++-- packages/block-library/src/image/image.js | 11 +----- .../block-library/src/video/tracks-editor.js | 5 +-- packages/url/README.md | 19 ++++++++++ packages/url/src/get-filename.js | 25 ++++++++++++ packages/url/src/index.js | 1 + packages/url/src/test/index.js | 38 +++++++++++++++++++ 7 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 packages/url/src/get-filename.js diff --git a/packages/block-library/src/file/transforms.js b/packages/block-library/src/file/transforms.js index 242b60adfd7354..3f96667b14745e 100644 --- a/packages/block-library/src/file/transforms.js +++ b/packages/block-library/src/file/transforms.js @@ -10,7 +10,7 @@ import { createBlobURL } from '@wordpress/blob'; import { createBlock } from '@wordpress/blocks'; import { select } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { getPath } from '@wordpress/url'; +import { getFilename } from '@wordpress/url'; const transforms = { from: [ @@ -71,11 +71,10 @@ const transforms = { type: 'block', blocks: [ 'core/image' ], transform: ( attributes ) => { - const filename = getPath( attributes.url )?.split( '/' ).pop(); - return createBlock( 'core/file', { href: attributes.url, - fileName: attributes.caption || filename, + fileName: + attributes.caption || getFilename( attributes.url ), textLinkHref: attributes.url, id: attributes.id, anchor: attributes.anchor, diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index b4cc2c29f6252a..dedd8786534be5 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, filter, map, last, pick, includes } from 'lodash'; +import { get, filter, map, pick, includes } from 'lodash'; /** * WordPress dependencies @@ -30,7 +30,7 @@ import { } from '@wordpress/block-editor'; import { useEffect, useState, useRef } from '@wordpress/element'; import { __, sprintf, isRTL } from '@wordpress/i18n'; -import { getPath } from '@wordpress/url'; +import { getFilename } from '@wordpress/url'; import { createBlock, switchToBlockType } from '@wordpress/blocks'; import { crop, overlayText, upload } from '@wordpress/icons'; import { store as noticesStore } from '@wordpress/notices'; @@ -49,13 +49,6 @@ import { isExternalImage } from './edit'; */ import { MIN_SIZE, ALLOWED_MEDIA_TYPES } from './constants'; -function getFilename( url ) { - const path = getPath( url ); - if ( path ) { - return last( path.split( '/' ) ); - } -} - export default function Image( { temporaryURL, attributes: { diff --git a/packages/block-library/src/video/tracks-editor.js b/packages/block-library/src/video/tracks-editor.js index 04f3f04f73cd05..9fcbbd5d4581c7 100644 --- a/packages/block-library/src/video/tracks-editor.js +++ b/packages/block-library/src/video/tracks-editor.js @@ -24,6 +24,7 @@ import { import { upload, media } from '@wordpress/icons'; import { useSelect } from '@wordpress/data'; import { useState } from '@wordpress/element'; +import { getFilename } from '@wordpress/url'; const ALLOWED_TYPES = [ 'text/vtt' ]; @@ -99,9 +100,7 @@ function TrackList( { tracks, onEditPress } ) { function SingleTrackEditor( { track, onChange, onClose, onRemove } ) { const { src = '', label = '', srcLang = '', kind = DEFAULT_KIND } = track; - const fileName = src.startsWith( 'blob:' ) - ? '' - : src.substring( src.lastIndexOf( '/' ) + 1 ); + const fileName = src.startsWith( 'blob:' ) ? '' : getFilename( src ) || ''; return (
diff --git a/packages/url/README.md b/packages/url/README.md index d6b338071b1b96..367ba4c0675677 100644 --- a/packages/url/README.md +++ b/packages/url/README.md @@ -132,6 +132,25 @@ _Returns_ - `string|void`: The authority part of the URL. +### getFilename + +Returns the filename part of the URL. + +_Usage_ + +```js +const filename1 = getFilename( 'http://localhost:8080/this/is/a/test.jpg' ); // 'test.jpg' +const filename2 = getFilename( '/this/is/a/test.png' ); // 'test.png' +``` + +_Parameters_ + +- _url_ `string`: The full URL. + +_Returns_ + +- `string|void`: The filename part of the URL. + ### getFragment Returns the fragment part of the URL. diff --git a/packages/url/src/get-filename.js b/packages/url/src/get-filename.js new file mode 100644 index 00000000000000..2941f18fe07b4d --- /dev/null +++ b/packages/url/src/get-filename.js @@ -0,0 +1,25 @@ +/** + * Returns the filename part of the URL. + * + * @param {string} url The full URL. + * + * @example + * ```js + * const filename1 = getFilename( 'http://localhost:8080/this/is/a/test.jpg' ); // 'test.jpg' + * const filename2 = getFilename( '/this/is/a/test.png' ); // 'test.png' + * ``` + * + * @return {string|void} The filename part of the URL. + */ +export function getFilename( url ) { + let filename; + try { + filename = new URL( url, 'http://example.com' ).pathname + .split( '/' ) + .pop(); + } catch ( error ) {} + + if ( filename ) { + return filename; + } +} diff --git a/packages/url/src/index.js b/packages/url/src/index.js index f060ae8152897d..eb4ee3237fab8e 100644 --- a/packages/url/src/index.js +++ b/packages/url/src/index.js @@ -22,3 +22,4 @@ export { safeDecodeURI } from './safe-decode-uri'; export { safeDecodeURIComponent } from './safe-decode-uri-component'; export { filterURLForDisplay } from './filter-url-for-display'; export { cleanForSlug } from './clean-for-slug'; +export { getFilename } from './get-filename'; diff --git a/packages/url/src/test/index.js b/packages/url/src/test/index.js index f4814b0b0bc83b..51bb69419bfa60 100644 --- a/packages/url/src/test/index.js +++ b/packages/url/src/test/index.js @@ -29,6 +29,7 @@ import { filterURLForDisplay, cleanForSlug, getQueryArgs, + getFilename, } from '../'; import wptData from './fixtures/wpt-data'; @@ -240,6 +241,7 @@ describe( 'isValidPath', () => { expect( isValidPath( 'relative/path' ) ).toBe( true ); expect( isValidPath( 'slightly/longer/path/' ) ).toBe( true ); expect( isValidPath( 'path/with/percent%20encoding' ) ).toBe( true ); + expect( isValidPath( '/' ) ).toBe( true ); } ); it( 'returns false if the path is invalid', () => { @@ -252,6 +254,42 @@ describe( 'isValidPath', () => { } ); } ); +describe( 'getFilename', () => { + it( 'returns the filename part of the URL', () => { + expect( getFilename( 'https://wordpress.org/image.jpg' ) ).toBe( + 'image.jpg' + ); + expect( + getFilename( 'https://wordpress.org/image.jpg?query=test' ) + ).toBe( 'image.jpg' ); + expect( getFilename( 'https://wordpress.org/image.jpg#anchor' ) ).toBe( + 'image.jpg' + ); + expect( + getFilename( 'http://localhost:8080/a/path/to/an/image.jpg' ) + ).toBe( 'image.jpg' ); + expect( getFilename( '/path/to/an/image.jpg' ) ).toBe( 'image.jpg' ); + expect( getFilename( 'path/to/an/image.jpg' ) ).toBe( 'image.jpg' ); + expect( getFilename( '/image.jpg' ) ).toBe( 'image.jpg' ); + expect( getFilename( 'image.jpg' ) ).toBe( 'image.jpg' ); + } ); + + it( 'returns undefined when the provided value does not contain a filename', () => { + expect( getFilename( 'http://localhost:8080/' ) ).toBe( undefined ); + expect( getFilename( 'http://localhost:8080/a/path/' ) ).toBe( + undefined + ); + expect( getFilename( 'http://localhost:8080/?query=test' ) ).toBe( + undefined + ); + expect( getFilename( 'http://localhost:8080/#anchor' ) ).toBe( + undefined + ); + expect( getFilename( 'a/path/' ) ).toBe( undefined ); + expect( getFilename( '/' ) ).toBe( undefined ); + } ); +} ); + describe( 'getQueryString', () => { it( 'returns the query string of a URL', () => { expect( From 807cac1dc98e4fc526d18a92f8b5978c8dc97619 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 27 Aug 2021 10:23:11 +0800 Subject: [PATCH 059/214] Fix button block focus trap after a URL has been added (#34314) * Rework button block link UI to match RichText format implementation * Refine some more, determine visibility by selection and url state * Add e2e test * Also focus rich text when unlinking using a keyboard shortcut --- packages/block-library/src/button/edit.js | 61 +++++++++++++------ .../specs/editor/blocks/buttons.test.js | 30 +++++++++ 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index fd7bb6edde87c3..0520144fb3b9ca 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { useCallback, useState, useRef } from '@wordpress/element'; +import { useCallback, useEffect, useState, useRef } from '@wordpress/element'; import { Button, ButtonGroup, @@ -73,27 +73,42 @@ function URLPicker( { opensInNewTab, onToggleOpenInNewTab, anchorRef, + richTextRef, } ) { - const [ isURLPickerOpen, setIsURLPickerOpen ] = useState( false ); - const urlIsSet = !! url; - const urlIsSetandSelected = urlIsSet && isSelected; - const openLinkControl = () => { - setIsURLPickerOpen( true ); - return false; // prevents default behaviour for event + const [ isEditingURL, setIsEditingURL ] = useState( false ); + const isURLSet = !! url; + + const startEditing = ( event ) => { + event.preventDefault(); + setIsEditingURL( true ); }; - const unlinkButton = () => { + + const unlink = () => { setAttributes( { url: undefined, linkTarget: undefined, rel: undefined, } ); - setIsURLPickerOpen( false ); + setIsEditingURL( false ); }; - const linkControl = ( isURLPickerOpen || urlIsSetandSelected ) && ( + + useEffect( () => { + if ( ! isSelected ) { + setIsEditingURL( false ); + } + }, [ isSelected ] ); + + const isLinkControlVisible = isSelected && ( isEditingURL || isURLSet ); + + const linkControl = isLinkControlVisible && ( setIsURLPickerOpen( false ) } + onClose={ () => { + setIsEditingURL( false ); + richTextRef.current?.focus(); + } } anchorRef={ anchorRef?.current } + focusOnMount={ isEditingURL ? 'firstElement' : false } > { + unlink(); + richTextRef.current?.focus(); + } } + forceIsEditingLink={ isEditingURL } /> ); + return ( <> - { ! urlIsSet && ( + { ! isURLSet && ( ) } - { urlIsSetandSelected && ( + { isURLSet && ( ) } @@ -138,8 +159,11 @@ function URLPicker( { { + unlink(); + richTextRef.current?.focus(); + }, } } /> ) } @@ -201,6 +225,7 @@ function ButtonEdit( props ) { const colorProps = useColorProps( attributes ); const spacingProps = useSpacingProps( attributes ); const ref = useRef(); + const richTextRef = useRef(); const blockProps = useBlockProps( { ref } ); return ( @@ -213,6 +238,7 @@ function ButtonEdit( props ) { } ) } > { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + it( 'moves focus from the link editor back to the button when escape is pressed after the URL has been submitted', async () => { + // Regression: https://github.com/WordPress/gutenberg/issues/34307 + await insertBlock( 'Buttons' ); + await pressKeyWithModifier( 'primary', 'k' ); + await page.waitForFunction( + () => !! document.activeElement.closest( '.block-editor-url-input' ) + ); + await page.keyboard.type( 'https://example.com' ); + await page.keyboard.press( 'Enter' ); + await page.waitForFunction( + () => + document.activeElement === + document.querySelector( + '.block-editor-link-control a[href="https://example.com"]' + ) + ); + await page.keyboard.press( 'Escape' ); + + // Focus should move from the link control to the button block's text. + await page.waitForFunction( + () => + document.activeElement === + document.querySelector( '[aria-label="Button text"]' ) + ); + + // The link control should still be visible when a URL is set. + const linkControl = await page.$( '.block-editor-link-control' ); + expect( linkControl ).toBeTruthy(); + } ); + it( 'can jump to the link editor using the keyboard shortcut', async () => { await insertBlock( 'Buttons' ); await page.keyboard.type( 'WordPress' ); From 46553eaea9b308b80786eadb1970d6c1ec609d0e Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 27 Aug 2021 19:23:08 +1000 Subject: [PATCH 060/214] i18n: Add context to 'none' strings for better translations (#34341) * Adding context to 'none' strings, and also implementing suggestions from #22095 This is because "None" can be translated differently in languages other than English depending on the context. * As always, my linter was asleep --- packages/block-library/src/audio/edit.js | 9 ++++++--- packages/block-library/src/audio/edit.native.js | 12 +++++++++--- packages/block-library/src/button/edit.native.js | 7 +++++-- packages/block-library/src/gallery/edit.js | 7 +++++-- packages/block-library/src/image/edit.native.js | 7 +++++-- .../block-library/src/video/edit-common-settings.js | 4 ++-- packages/components/src/dimension-control/sizes.js | 12 ++++++------ 7 files changed, 38 insertions(+), 20 deletions(-) diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index c2fe191252a931..22b0ad592eb484 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -20,7 +20,7 @@ import { store as blockEditorStore, } from '@wordpress/block-editor'; import { useEffect } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { audio as icon } from '@wordpress/icons'; import { createBlock } from '@wordpress/blocks'; @@ -157,7 +157,7 @@ function AudioEdit( { checked={ loop } /> @@ -169,7 +169,10 @@ function AudioEdit( { { value: '', label: __( 'Browser default' ) }, { value: 'auto', label: __( 'Auto' ) }, { value: 'metadata', label: __( 'Metadata' ) }, - { value: 'none', label: __( 'None' ) }, + { + value: 'none', + label: _x( 'None', '"Preload" value' ), + }, ] } /> diff --git a/packages/block-library/src/audio/edit.native.js b/packages/block-library/src/audio/edit.native.js index a46a3b57e46e35..a86abf369bd03c 100644 --- a/packages/block-library/src/audio/edit.native.js +++ b/packages/block-library/src/audio/edit.native.js @@ -26,7 +26,7 @@ import { MediaUploadProgress, store as blockEditorStore, } from '@wordpress/block-editor'; -import { __, sprintf } from '@wordpress/i18n'; +import { __, _x, sprintf } from '@wordpress/i18n'; import { audio as icon, replace } from '@wordpress/icons'; import { useState } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; @@ -197,7 +197,10 @@ function AudioEdit( { checked={ loop } /> @@ -209,7 +212,10 @@ function AudioEdit( { { value: '', label: __( 'Browser default' ) }, { value: 'auto', label: __( 'Auto' ) }, { value: 'metadata', label: __( 'Metadata' ) }, - { value: 'none', label: __( 'None' ) }, + { + value: 'none', + label: _x( 'None', '"Preload" value' ), + }, ] } hideCancelButton={ true } /> diff --git a/packages/block-library/src/button/edit.native.js b/packages/block-library/src/button/edit.native.js index a06f5d06adccbe..ec9741fe5add4d 100644 --- a/packages/block-library/src/button/edit.native.js +++ b/packages/block-library/src/button/edit.native.js @@ -6,7 +6,7 @@ import { View, AccessibilityInfo, Platform, Text } from 'react-native'; * WordPress dependencies */ import { withInstanceId, compose } from '@wordpress/compose'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { RichText, InspectorControls, @@ -136,7 +136,10 @@ class ButtonEdit extends Component { }, linkRel: { label: __( 'Link Rel' ), - placeholder: __( 'None' ), + placeholder: _x( + 'None', + 'Link rel attribute value placeholder' + ), }, }; diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 3a1fa9c990b952..ec4f274751580c 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -24,7 +24,7 @@ import { useBlockProps, } from '@wordpress/block-editor'; import { Platform, useEffect, useMemo } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; +import { __, _x, sprintf } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; import { withViewportMatch } from '@wordpress/viewport'; import { View } from '@wordpress/primitives'; @@ -57,7 +57,10 @@ const MAX_COLUMNS = 8; const linkOptions = [ { value: LINK_DESTINATION_ATTACHMENT, label: __( 'Attachment Page' ) }, { value: LINK_DESTINATION_MEDIA, label: __( 'Media File' ) }, - { value: LINK_DESTINATION_NONE, label: __( 'None' ) }, + { + value: LINK_DESTINATION_NONE, + label: _x( 'None', 'Media item link option' ), + }, ]; const ALLOWED_MEDIA_TYPES = [ 'image' ]; diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 54174c3607a74b..1233abbcdca605 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -42,7 +42,7 @@ import { BlockStyles, store as blockEditorStore, } from '@wordpress/block-editor'; -import { __, sprintf } from '@wordpress/i18n'; +import { __, _x, sprintf } from '@wordpress/i18n'; import { getProtocol, hasQueryArg } from '@wordpress/url'; import { doAction, hasAction } from '@wordpress/hooks'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; @@ -121,7 +121,10 @@ export class ImageEdit extends Component { }, linkRel: { label: __( 'Link Rel' ), - placeholder: __( 'None' ), + placeholder: _x( + 'None', + 'Link rel attribute value placeholder' + ), }, }; } diff --git a/packages/block-library/src/video/edit-common-settings.js b/packages/block-library/src/video/edit-common-settings.js index 74067cbd7dd8d4..5cd74c41bec92a 100644 --- a/packages/block-library/src/video/edit-common-settings.js +++ b/packages/block-library/src/video/edit-common-settings.js @@ -1,14 +1,14 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { ToggleControl, SelectControl } from '@wordpress/components'; import { useMemo, useCallback, Platform } from '@wordpress/element'; const options = [ { value: 'auto', label: __( 'Auto' ) }, { value: 'metadata', label: __( 'Metadata' ) }, - { value: 'none', label: __( 'None' ) }, + { value: 'none', label: _x( 'None', 'Preload value' ) }, ]; const VideoSettings = ( { setAttributes, attributes } ) => { diff --git a/packages/components/src/dimension-control/sizes.js b/packages/components/src/dimension-control/sizes.js index 4a77ef25ff7734..55dcff9698d4e4 100644 --- a/packages/components/src/dimension-control/sizes.js +++ b/packages/components/src/dimension-control/sizes.js @@ -10,7 +10,7 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { _x } from '@wordpress/i18n'; /** * Finds the correct size object from the provided sizes @@ -26,23 +26,23 @@ export const findSizeBySlug = ( sizes, slug ) => export default [ { - name: __( 'None' ), + name: _x( 'None', 'Size of a UI element' ), slug: 'none', }, { - name: __( 'Small' ), + name: _x( 'Small', 'Size of a UI element' ), slug: 'small', }, { - name: __( 'Medium' ), + name: _x( 'Medium', 'Size of a UI element' ), slug: 'medium', }, { - name: __( 'Large' ), + name: _x( 'Large', 'Size of a UI element' ), slug: 'large', }, { - name: __( 'Extra Large' ), + name: _x( 'Extra Large', 'Size of a UI element' ), slug: 'xlarge', }, ]; From f42fa3aaaaf8a0a6feca9cced6f9d0e6beb49cdd Mon Sep 17 00:00:00 2001 From: Jeremy Yip Date: Fri, 27 Aug 2021 02:25:40 -0700 Subject: [PATCH 061/214] Global Styles: Fix block-level global styles color panels (#34293) Check if block color support have been explicitly opted out BackgroundColor and color are opt-out block supports: they're enabled if there's support for any color unless the block opts-out from them explicitly. Global styles UI panels were't respecting this opt out process, which is why we add logic to validate whether or not a block has turned off background color and color supports. --- packages/blocks/src/api/constants.js | 6 +- .../editor/global-styles-provider.js | 20 ++- .../editor/test/global-styles-provider.js | 131 ++++++++++++++++++ 3 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 packages/edit-site/src/components/editor/test/global-styles-provider.js diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index 18342aa9162565..d7ce7cafd25261 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -24,7 +24,8 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, backgroundColor: { value: [ 'color', 'background' ], - support: [ 'color' ], + support: [ 'color', 'background' ], + requiresOptOut: true, }, borderColor: { value: [ 'border', 'color' ], @@ -50,7 +51,8 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, color: { value: [ 'color', 'text' ], - support: [ 'color' ], + support: [ 'color', 'text' ], + requiresOptOut: true, }, linkColor: { value: [ 'elements', 'link', 'color', 'text' ], diff --git a/packages/edit-site/src/components/editor/global-styles-provider.js b/packages/edit-site/src/components/editor/global-styles-provider.js index 05e672dfa05f17..d85aecdf251bcc 100644 --- a/packages/edit-site/src/components/editor/global-styles-provider.js +++ b/packages/edit-site/src/components/editor/global-styles-provider.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { set, get, mergeWith, mapValues, setWith, clone } from 'lodash'; +import { set, get, has, mergeWith, mapValues, setWith, clone } from 'lodash'; /** * WordPress dependencies @@ -75,8 +75,24 @@ export const useGlobalStylesReset = () => { const extractSupportKeys = ( supports ) => { const supportKeys = []; Object.keys( STYLE_PROPERTY ).forEach( ( name ) => { + if ( ! STYLE_PROPERTY[ name ].support ) { + return; + } + + // Opting out means that, for certain support keys like background color, + // blocks have to explicitly set the support value false. If the key is + // unset, we still enable it. + if ( STYLE_PROPERTY[ name ].requiresOptOut ) { + if ( + has( supports, STYLE_PROPERTY[ name ].support[ 0 ] ) && + get( supports, STYLE_PROPERTY[ name ].support ) !== false + ) { + return supportKeys.push( name ); + } + } + if ( get( supports, STYLE_PROPERTY[ name ].support, false ) ) { - supportKeys.push( name ); + return supportKeys.push( name ); } } ); return supportKeys; diff --git a/packages/edit-site/src/components/editor/test/global-styles-provider.js b/packages/edit-site/src/components/editor/test/global-styles-provider.js new file mode 100644 index 00000000000000..0a31516576e572 --- /dev/null +++ b/packages/edit-site/src/components/editor/test/global-styles-provider.js @@ -0,0 +1,131 @@ +/** + * WordPress dependencies + */ +import { dispatch } from '@wordpress/data'; + +/** + * External dependencies + */ +import { mount } from 'enzyme'; +import { act } from 'react-dom/test-utils'; + +/** + * Internal dependencies + */ +import GlobalStylesProvider, { + useGlobalStylesContext, +} from '../global-styles-provider'; + +const settings = { + styles: [ + { + css: 'body {\n\tmargin: 0;\n\tpadding: 0;\n}', + baseURL: 'http://localhost:4759/ponyfill.css', + }, + ], + __experimentalGlobalStylesBaseStyles: {}, +}; + +const generateCoverBlockType = ( colorSupports ) => { + return { + name: 'core/cover', + supports: { + color: colorSupports, + }, + }; +}; + +const FakeCmp = () => { + const globalStylesContext = useGlobalStylesContext(); + const coverBlockSupports = + globalStylesContext.blocks[ 'core/cover' ].supports; + + return
; +}; + +const generateWrapper = () => { + return mount( + + + + ); +}; + +describe( 'global styles provider', () => { + beforeAll( () => { + dispatch( 'core/edit-site' ).updateSettings( settings ); + } ); + + describe( 'when a block enables color support', () => { + describe( 'and disables background color support', () => { + it( 'still enables text color support', () => { + act( () => { + dispatch( 'core/blocks' ).addBlockTypes( + generateCoverBlockType( { + link: true, + background: false, + } ) + ); + } ); + + const wrapper = generateWrapper(); + const actual = wrapper + .findWhere( ( ele ) => Boolean( ele.prop( 'supports' ) ) ) + .prop( 'supports' ); + expect( actual ).not.toContain( 'backgroundColor' ); + expect( actual ).toContain( 'color' ); + + act( () => { + dispatch( 'core/blocks' ).removeBlockTypes( 'core/cover' ); + } ); + } ); + } ); + + describe( 'and both text color and background color support are disabled', () => { + it( 'disables text color and background color support', () => { + act( () => { + dispatch( 'core/blocks' ).addBlockTypes( + generateCoverBlockType( { + text: false, + background: false, + } ) + ); + } ); + + const wrapper = generateWrapper(); + const actual = wrapper + .findWhere( ( ele ) => Boolean( ele.prop( 'supports' ) ) ) + .prop( 'supports' ); + expect( actual ).not.toContain( 'backgroundColor' ); + expect( actual ).not.toContain( 'color' ); + + act( () => { + dispatch( 'core/blocks' ).removeBlockTypes( 'core/cover' ); + } ); + } ); + } ); + + describe( 'and text color and background color supports are omitted', () => { + it( 'still enables both text color and background color supports', () => { + act( () => { + dispatch( 'core/blocks' ).addBlockTypes( + generateCoverBlockType( { link: true } ) + ); + } ); + + const wrapper = generateWrapper(); + const actual = wrapper + .findWhere( ( ele ) => Boolean( ele.prop( 'supports' ) ) ) + .prop( 'supports' ); + expect( actual ).toContain( 'backgroundColor' ); + expect( actual ).toContain( 'color' ); + + act( () => { + dispatch( 'core/blocks' ).removeBlockTypes( 'core/cover' ); + } ); + } ); + } ); + } ); +} ); From 662dabcaa7035276a3428f7271407f5b8443d9a9 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Fri, 27 Aug 2021 12:31:01 +0200 Subject: [PATCH 062/214] Fix navigation block classname issues (#34344) * Fix missing class renames. * Add readme notes. * Fix flex issue. --- packages/block-library/src/navigation-link/editor.scss | 2 +- packages/block-library/src/navigation/editor.scss | 7 ++++--- .../block-library/src/navigation/placeholder-preview.js | 6 +++--- packages/components/src/navigation/README.md | 9 +++++++++ .../edit-navigation/src/components/editor/style.scss | 8 ++++---- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/block-library/src/navigation-link/editor.scss b/packages/block-library/src/navigation-link/editor.scss index e11e9c949381c3..8f3c08717e5dd4 100644 --- a/packages/block-library/src/navigation-link/editor.scss +++ b/packages/block-library/src/navigation-link/editor.scss @@ -41,7 +41,7 @@ * Navigation Items. */ -.wp-block-navigation-link { +.wp-block-navigation-item { .wp-block-navigation__submenu-container { display: block; } diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss index 47fa26ce4920cc..e85c873c700e1b 100644 --- a/packages/block-library/src/navigation/editor.scss +++ b/packages/block-library/src/navigation/editor.scss @@ -31,7 +31,7 @@ // Low specificity default to ensure background color applies to submenus. .wp-block-navigation__container, -.wp-block-navigation-link { +.wp-block-navigation-item { background-color: inherit; } @@ -215,7 +215,7 @@ $color-control-label-height: 20px; // Style skeleton elements to mostly match the metrics of actual menu items. // Needs specificity. - .wp-block-navigation-link.wp-block-navigation-link { + .wp-block-navigation-item.wp-block-navigation-item { position: relative; min-width: 72px; @@ -237,7 +237,7 @@ $color-control-label-height: 20px; } - .wp-block-navigation-link.wp-block-navigation-link, + .wp-block-navigation-item.wp-block-navigation-item, .wp-block-navigation-placeholder__preview-search-icon { opacity: 0.3; } @@ -250,6 +250,7 @@ $color-control-label-height: 20px; width: 0; overflow: hidden; flex-wrap: nowrap; + flex: 0; } // Hide entirely when vertical. diff --git a/packages/block-library/src/navigation/placeholder-preview.js b/packages/block-library/src/navigation/placeholder-preview.js index fff5f80b39f069..a8c1ae6bdd451f 100644 --- a/packages/block-library/src/navigation/placeholder-preview.js +++ b/packages/block-library/src/navigation/placeholder-preview.js @@ -6,9 +6,9 @@ import { Icon, search } from '@wordpress/icons'; const PlaceholderPreview = () => { return ( ` closing tag. ([34077](https://github.com/WordPress/gutenberg/pull/34077)) - Post Excerpt: remove interactive formatting. ([34083](https://github.com/WordPress/gutenberg/pull/34083)) - - RichText: fix Space key for button and summary elements. ([30244](https://github.com/WordPress/gutenberg/pull/30244)) + - RichText: fix `space` key for button and summary elements. ([30244](https://github.com/WordPress/gutenberg/pull/30244)) - Search Block: add space between generated border class names. ([34025](https://github.com/WordPress/gutenberg/pull/34025)) +- Build Tooling + - Webpack: Fix watch on `.json` and `.php` files. ([34024](https://github.com/WordPress/gutenberg/pull/34024)) + - Pin TypeScript dependency to a specific version to avoid pulling in breaking changes. ([34422](https://github.com/WordPress/gutenberg/pull/34422)) +- Components + - Fix RTL on `Flex` component. ([33729](https://github.com/WordPress/gutenberg/pull/33729)) + - NavigationSidebar: fix template content for content-navigation-item preview. ([34203](https://github.com/WordPress/gutenberg/pull/34203)) + - Remove deprecated import style for storybook/addon-docs. ([34095](https://github.com/WordPress/gutenberg/pull/34095)) + - ToolsPanel: add tools panel item deregistration. ([34085](https://github.com/WordPress/gutenberg/pull/34085)) + - Post Title: remove wrapper div and fix border style. ([34167](https://github.com/WordPress/gutenberg/pull/34167)) +- Core Data + - `GetEntityRecords` returns items even if some included IDs don't exist. ([34034](https://github.com/WordPress/gutenberg/pull/34034)) - Design Tools - - Allow zero values for Theme JSON styles. ([34251](https://github.com/WordPress/gutenberg/pull/34251)) + - Allow zero values for `theme.json` styles. ([34251](https://github.com/WordPress/gutenberg/pull/34251)) - Global Styles - - Site editor: Fix for how CSS Custom Properties are generated. ([33932](https://github.com/WordPress/gutenberg/pull/33932)) + - Site editor: fix for how CSS custom properties are generated. ([33932](https://github.com/WordPress/gutenberg/pull/33932)) - Packages - Rich Text: add check to `toTree()` in replacements before accessing its type. ([34020](https://github.com/WordPress/gutenberg/pull/34020)) - Post Editor @@ -81,50 +82,59 @@ ### Experiments - Block API - - Block Editor: Absorb parent block toolbar controls. ([33955](https://github.com/WordPress/gutenberg/pull/33955)) - - Block Editor: Use groups for InspectorControls. ([34069](https://github.com/WordPress/gutenberg/pull/34069)) + - Block Editor: absorb parent block toolbar controls. ([33955](https://github.com/WordPress/gutenberg/pull/33955)) + - Block Editor: use groups for InspectorControls. ([34069](https://github.com/WordPress/gutenberg/pull/34069)) - Block Library - Add generic classnames to children of Navigation. ([33918](https://github.com/WordPress/gutenberg/pull/33918)) - Global Styles - Add slashes back to the Theme JSON. ([33919](https://github.com/WordPress/gutenberg/pull/33919)) - - Add block spacing gap configuration to theme.json and add support for this CSS variable to the "flow/default" layout. ([33812](https://github.com/WordPress/gutenberg/pull/33812)) + - Add block spacing gap configuration to `theme.json` and add support for this CSS variable to the "flow/default" layout. ([33812](https://github.com/WordPress/gutenberg/pull/33812)) ### Documentation -- Packages - - Add documentation for mobile components directory. ([33872](https://github.com/WordPress/gutenberg/pull/33872)) - Handbook - Alphabetize glossary entries. ([34058](https://github.com/WordPress/gutenberg/pull/34058)) - - Correct minor typos in wp-plugin.md ([34185](https://github.com/WordPress/gutenberg/pull/34185)) + - Correct minor typos in `wp-plugin.md` ([34185](https://github.com/WordPress/gutenberg/pull/34185)) - Remove extraneous params from `block_type_metadata` hook. ([34151](https://github.com/WordPress/gutenberg/pull/34151)) - - Update incorrect Settings examples in "Global Settings & Styles". ([34084](https://github.com/WordPress/gutenberg/pull/34084)) - - Use block.json to add attributes in create block tutorial. ([33978](https://github.com/WordPress/gutenberg/pull/33978)) - - Fix typo in block gap documentation in theme-json.md. ([34231](https://github.com/WordPress/gutenberg/pull/34231)) - - Fix broken mobile testing documentation link in testing-overview.md . ([34187](https://github.com/WordPress/gutenberg/pull/34187)) - - Fix typo in legacy-widget-block.md. ([34103](https://github.com/WordPress/gutenberg/pull/34103)) - - Update spelling and `fontSize` examples in create-block-theme.md. ([34152](https://github.com/WordPress/gutenberg/pull/34152)) + - Update incorrect settings examples in "Global Settings & Styles". ([34084](https://github.com/WordPress/gutenberg/pull/34084)) + - Use `block.json` to add attributes in create block tutorial. ([33978](https://github.com/WordPress/gutenberg/pull/33978)) + - Fix typo in block gap documentation in `theme-json.md`. ([34231](https://github.com/WordPress/gutenberg/pull/34231)) + - Fix broken mobile testing documentation link in `testing-overview.md`. ([34187](https://github.com/WordPress/gutenberg/pull/34187)) + - Fix typo in `legacy-widget-block.md`. ([34103](https://github.com/WordPress/gutenberg/pull/34103)) + - Update spelling and `fontSize` examples in `create-block-theme.md`. ([34152](https://github.com/WordPress/gutenberg/pull/34152)) - Library - - Bump mobile version in experiments page for gallery. ([34220](https://github.com/WordPress/gutenberg/pull/34220)) + - Bump mobile version in experiments page for Gallery. ([34220](https://github.com/WordPress/gutenberg/pull/34220)) +- Packages + - Add documentation for mobile components directory. ([33872](https://github.com/WordPress/gutenberg/pull/33872)) ### Code Quality - Block Editor - Render head and body with single portal for block previews. ([34208](https://github.com/WordPress/gutenberg/pull/34208)) - BlockList: refactor element context for style/svg appending. ([34183](https://github.com/WordPress/gutenberg/pull/34183)) - - BlockList: Use InnerBlocks internally. ([29895](https://github.com/WordPress/gutenberg/pull/29895)) -- Site Editor - - Remove extra dom element used for template part overlay. ([34012](https://github.com/WordPress/gutenberg/pull/34012)) + - BlockList: use InnerBlocks internally. ([29895](https://github.com/WordPress/gutenberg/pull/29895)) - Components - Unit Control: add unit tests for `getValidParsedUnit` utility method. ([34029](https://github.com/WordPress/gutenberg/pull/34029)) - Rename `SegmentedControl` to `ToggleGroupControl`. ([34111](https://github.com/WordPress/gutenberg/pull/34111)) - Dropdown Menu: remove min-width from the dropdown component and add whitespace rule to avoid wrapping ([33995](https://github.com/WordPress/gutenberg/pull/33995)) - Core Data - Allow passing store definitions to controls. ([34170](https://github.com/WordPress/gutenberg/pull/34170)) +- Site Editor + - Remove extra DOM element used for template part overlay. ([34012](https://github.com/WordPress/gutenberg/pull/34012)) ### Tools +- Build Tooling + - Automated Changelog: force group all documentation tasks under `Documentation`. ([34042](https://github.com/WordPress/gutenberg/pull/34042)) + - Automated Changelog: rename "Editor" grouping to "Post Editor" to avoid ambiguity with other editors. ([34093](https://github.com/WordPress/gutenberg/pull/34093)) + - Automated Changelog: sort feature groups by issue name. ([34071](https://github.com/WordPress/gutenberg/pull/34071)) + - Automated Changelog: use nested headings for feature groups instead of indenting lists. ([34040](https://github.com/WordPress/gutenberg/pull/34040)) + - Automated Changelog: remove `Uncategorized` header in output and place items at top. ([34037](https://github.com/WordPress/gutenberg/pull/34037)) + - Add Typescript extensions to watched files. ([34094](https://github.com/WordPress/gutenberg/pull/34094)) + - Remove obsolete step that pushes tags in NPM publishing flow. ([34114](https://github.com/WordPress/gutenberg/pull/34114)) + - Release workflow: only commit modified changelogs. ([34211](https://github.com/WordPress/gutenberg/pull/34211)) - ESLint - - Eslint plugin: Use @typescript-eslint/no-duplicate-imports in TS projects. ([34055](https://github.com/WordPress/gutenberg/pull/34055)) + - Eslint plugin: use `@typescript-eslint/no-duplicate-imports` in TS projects. ([34055](https://github.com/WordPress/gutenberg/pull/34055)) - GitHub Contributor Templates - - Issue Forms: Simplify the bug report form template. ([34007](https://github.com/WordPress/gutenberg/pull/34007)) + - Issue Forms: simplify the bug report form template. ([34007](https://github.com/WordPress/gutenberg/pull/34007)) - Logs - Hide deprecation logs under a console group. ([34163](https://github.com/WordPress/gutenberg/pull/34163)) - Testing @@ -134,14 +144,6 @@ - Remove the `ENVIRONMENT_DIRECTORY` env variable that was added to the performance jobs. ([34086](https://github.com/WordPress/gutenberg/pull/34086)) - Add snapshot test for changelog formatting. ([34049](https://github.com/WordPress/gutenberg/pull/34049)) - Experiment with using REST API in end-to-end tests to build up states. ([33414](https://github.com/WordPress/gutenberg/pull/33414)) -- Build Tooling - - Automated Changelog: force group all documentation tasks under `Documentation`. ([34042](https://github.com/WordPress/gutenberg/pull/34042)) - - Automated Changelog: rename "Editor" grouping to "Post Editor" to avoid ambiguity with other editors. ([34093](https://github.com/WordPress/gutenberg/pull/34093)) - - Automated Changelog: sort feature groups by issue name. ([34071](https://github.com/WordPress/gutenberg/pull/34071)) - - Automated Changelog: use nested headings for feature groups instead of indenting lists. ([34040](https://github.com/WordPress/gutenberg/pull/34040)) - - Automated Changelog: remove `Uncategorized` header in output and place items at top. ([34037](https://github.com/WordPress/gutenberg/pull/34037)) - - Add Typescript extensions to watched files. ([34094](https://github.com/WordPress/gutenberg/pull/34094)) - - Remove obsolete step that pushes tags in npm publishing flow. ([34114](https://github.com/WordPress/gutenberg/pull/34114)) From 011416401387c15d6df981783b6b08484d5809ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Wed, 1 Sep 2021 08:56:02 +0200 Subject: [PATCH 092/214] Block Editor: Ensure that `blockType` is defined when accessing `apiVersion` (#34346) --- packages/block-editor/src/components/block-list/block.js | 4 ++-- .../block-list/use-block-props/use-block-custom-class-name.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 8b8ccc82dd0da1..c30921c4771347 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -111,11 +111,11 @@ function BlockListBlock( { const blockType = getBlockType( name ); const lightBlockWrapper = - blockType.apiVersion > 1 || + blockType?.apiVersion > 1 || hasBlockSupport( blockType, 'lightBlockWrapper', false ); // Determine whether the block has props to apply to the wrapper. - if ( blockType.getEditWrapperProps ) { + if ( blockType?.getEditWrapperProps ) { wrapperProps = mergeWrapperProps( wrapperProps, blockType.getEditWrapperProps( attributes ) diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-block-custom-class-name.js b/packages/block-editor/src/components/block-list/use-block-props/use-block-custom-class-name.js index 205d5de9c5594f..9b1ea0dfa0953f 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-block-custom-class-name.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-block-custom-class-name.js @@ -33,7 +33,7 @@ export function useBlockCustomClassName( clientId ) { const blockType = getBlockType( getBlockName( clientId ) ); const hasLightBlockWrapper = - blockType.apiVersion > 1 || + blockType?.apiVersion > 1 || hasBlockSupport( blockType, 'lightBlockWrapper', false ); if ( ! hasLightBlockWrapper ) { From da36e6ad2cd0d5ea312110d39e9541e1af6b3c1b Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Wed, 1 Sep 2021 09:48:30 +0200 Subject: [PATCH 093/214] Fix padding regression. (#34435) --- .../src/components/media-replace-flow/style.scss | 14 ++++---------- .../src/components/tool-selector/style.scss | 10 +++++----- packages/components/src/dropdown-menu/style.scss | 12 ++++++------ 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/packages/block-editor/src/components/media-replace-flow/style.scss b/packages/block-editor/src/components/media-replace-flow/style.scss index 110194f490b46f..f9547779548296 100644 --- a/packages/block-editor/src/components/media-replace-flow/style.scss +++ b/packages/block-editor/src/components/media-replace-flow/style.scss @@ -5,22 +5,16 @@ display: none; } -// Forcing some space above the list of options in -// the dropdown to visually balance them. -.block-editor-media-replace-flow__options .components-popover__content > div { - padding-top: $grid-unit-20; -} - .block-editor-media-replace-flow__indicator { margin-left: 4px; } .block-editor-media-flow__url-input { border-top: $border-width solid $gray-900; - margin-top: $grid-unit-15; - margin-right: -$grid-unit-15; - margin-left: -$grid-unit-15; - padding: $grid-unit-15 $grid-unit-30 0; + margin-top: $grid-unit-10; + margin-right: -$grid-unit-10; + margin-left: -$grid-unit-10; + padding: $grid-unit-20; .block-editor-media-replace-flow__image-url-label { display: block; diff --git a/packages/block-editor/src/components/tool-selector/style.scss b/packages/block-editor/src/components/tool-selector/style.scss index ad605ef037fe79..03774fe0f6b9d3 100644 --- a/packages/block-editor/src/components/tool-selector/style.scss +++ b/packages/block-editor/src/components/tool-selector/style.scss @@ -1,10 +1,10 @@ .block-editor-tool-selector__help { margin-top: $grid-unit-10; - margin-left: -$grid-unit-15; - margin-right: -$grid-unit-15; - margin-bottom: -$grid-unit-15; - padding: $grid-unit-15 ($grid-unit-15 + $grid-unit-10); - border-top: 1px solid $gray-300; + margin-left: -$grid-unit-10; + margin-right: -$grid-unit-10; + margin-bottom: -$grid-unit-10; + padding: $grid-unit-20; + border-top: $border-width solid $gray-300; color: $gray-700; min-width: 280px; } diff --git a/packages/components/src/dropdown-menu/style.scss b/packages/components/src/dropdown-menu/style.scss index 60e1a91270294f..64303ea9049eb7 100644 --- a/packages/components/src/dropdown-menu/style.scss +++ b/packages/components/src/dropdown-menu/style.scss @@ -61,25 +61,25 @@ } .components-menu-group { - padding: $grid-unit-15; + padding: $grid-unit-10; margin-top: 0; margin-bottom: 0; - margin-left: -$grid-unit-15; - margin-right: -$grid-unit-15; + margin-left: -$grid-unit-10; + margin-right: -$grid-unit-10; &:first-child { - margin-top: -$grid-unit-15; + margin-top: -$grid-unit-10; } &:last-child { - margin-bottom: -$grid-unit-15; + margin-bottom: -$grid-unit-10; } } .components-menu-group + .components-menu-group { margin-top: 0; border-top: $border-width solid $gray-400; - padding: $grid-unit-15; + padding: $grid-unit-10; .is-alternate & { border-color: $gray-900; From c427574a5797e94bde4ee25b25c527bbc4089788 Mon Sep 17 00:00:00 2001 From: SeanMcMillan Date: Wed, 1 Sep 2021 04:28:00 -0400 Subject: [PATCH 094/214] Fix build hang on Windows 10 (#23589) On Windows 10, I seem to be unable to build. The build completion rises to 100%, but the process never finishes. I can build if I manually set the number of workers in the farm to 4, but not if it's any higher. It appears that some files are not being counted as "complete". It's probably files that finish before the "ended" signal is recieved (meaning everything has been queued.) Moving the increment out of the conditional fixes this. --- bin/packages/build.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/packages/build.js b/bin/packages/build.js index eff2fcd732020b..250492506ae7d2 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -263,7 +263,8 @@ stream console.error( error ); } - if ( ended && ++complete === files.length ) { + ++complete; + if ( ended && complete === files.length ) { workerFarm.end( worker ); } } ) From 003d83c188a9d2a72567feafb6a6702529c3f7b9 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Wed, 1 Sep 2021 11:41:10 +0300 Subject: [PATCH 095/214] [Block Editor]: Fix caret position on block merging (#34169) * [Block Editor]: Fix caret position on block merging * add e2e tests * update e2e tests --- packages/block-editor/src/store/actions.js | 3 +- packages/block-editor/src/store/reducer.js | 6 +-- .../block-editor/src/store/test/reducer.js | 24 ++++++++++ .../editor/various/splitting-merging.test.js | 46 +++++++++++++++++++ 4 files changed, 75 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index cd09e91dfbef9c..38dd3500e7d871 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -885,7 +885,8 @@ export function* mergeBlocks( firstBlockClientId, secondBlockClientId ) { }, }, ...blocksWithTheSameType.slice( 1 ), - ] + ], + 0 // If we don't pass the `indexToSelect` it will default to the last block. ); } diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 4c18f041c7948f..4fd2e38efa0d4d 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1281,9 +1281,9 @@ function selectionHelper( state = {}, action ) { return state; } - const indexToSelect = - action.indexToSelect || action.blocks.length - 1; - const blockToSelect = action.blocks[ indexToSelect ]; + const blockToSelect = + action.blocks[ action.indexToSelect ] || + action.blocks[ action.blocks.length - 1 ]; if ( ! blockToSelect ) { return {}; diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index 2015ca50d00e13..e3c7cdbc05bf21 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -2397,6 +2397,30 @@ describe( 'state', () => { expect( state.selectionEnd ).toEqual( expected.selectionEnd ); } ); + it( 'should replace the selected block when is explicitly passed (`indexToSelect`)', () => { + const original = deepFreeze( { + selectionStart: { clientId: 'chicken' }, + selectionEnd: { clientId: 'chicken' }, + } ); + const action = { + type: 'REPLACE_BLOCKS', + clientIds: [ 'chicken' ], + blocks: [ + { clientId: 'rigas' }, + { clientId: 'chicken' }, + { clientId: 'wings' }, + ], + indexToSelect: 0, + }; + const state = selection( original, action ); + expect( state ).toEqual( + expect.objectContaining( { + selectionStart: { clientId: 'rigas' }, + selectionEnd: { clientId: 'rigas' }, + } ) + ); + } ); + it( 'should reset if replacing with empty set', () => { const original = deepFreeze( { selectionStart: { clientId: 'chicken' }, diff --git a/packages/e2e-tests/specs/editor/various/splitting-merging.test.js b/packages/e2e-tests/specs/editor/various/splitting-merging.test.js index 05e08db82afce6..3317957ff5f1b7 100644 --- a/packages/e2e-tests/specs/editor/various/splitting-merging.test.js +++ b/packages/e2e-tests/specs/editor/various/splitting-merging.test.js @@ -226,4 +226,50 @@ describe( 'splitting and merging blocks', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + describe( 'test restore selection when merge produces more than one block', () => { + it( 'on forward delete', async () => { + await insertBlock( 'Paragraph' ); + await page.keyboard.type( 'hi' ); + await insertBlock( 'List' ); + await page.keyboard.type( 'item 1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'item 2' ); + await pressKeyTimes( 'ArrowUp', 2 ); + await page.keyboard.press( 'Delete' ); + // Carret should be in the first block and at the proper position. + await page.keyboard.type( '-' ); + expect( await getEditedPostContent() ).toMatchInlineSnapshot( ` + " +

hi-item 1

+ + + +

item 2

+ " + ` ); + } ); + it( 'on backspace', async () => { + await insertBlock( 'Paragraph' ); + await page.keyboard.type( 'hi' ); + await insertBlock( 'List' ); + await page.keyboard.type( 'item 1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'item 2' ); + await page.keyboard.press( 'ArrowUp' ); + await pressKeyTimes( 'ArrowLeft', 6 ); + await page.keyboard.press( 'Backspace' ); + // Carret should be in the first block and at the proper position. + await page.keyboard.type( '-' ); + expect( await getEditedPostContent() ).toMatchInlineSnapshot( ` + " +

hi-item 1

+ + + +

item 2

+ " + ` ); + } ); + } ); } ); From bdb92729488eb33aed0b2ed949e9236153911f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Wed, 1 Sep 2021 11:42:28 +0200 Subject: [PATCH 096/214] Tools: Update TypeScript to the 4.4.x version (#34373) * Tools: Update TypeScript to the 4.4.x version See: https://devblogs.microsoft.com/typescript/announcing-typescript-4-4/ * Fix unknow type in catch variables * Use unknown errors in input-field state * skipLibCheck to prevent Reakit type errors from causing issues * Use lodash DebouncedFunc for throttled/debounced functions * Restore FireFox-only caretPositionFromPoint type See https://unpkg.com/browse/@types/web@0.0.1/index.d.ts * Disable useUnknownInCatchVariables for lazy-import * Fix unknown catch variable in project-management-automation * Relax the check in catch clause for hasWordPressProfile call Co-authored-by: Jon Surrell --- bin/plugin/commands/changelog.js | 4 +- package-lock.json | 110 +++++++++--------- package.json | 27 +++-- .../src/input-control/reducer/actions.ts | 5 +- .../src/input-control/reducer/reducer.ts | 2 +- .../src/input-control/reducer/state.ts | 2 +- packages/components/tsconfig.json | 5 +- packages/compose/README.md | 4 +- packages/compose/package.json | 4 +- .../compose/src/hooks/use-debounce/index.js | 2 +- .../compose/src/hooks/use-throttle/index.js | 2 +- .../dom/src/dom/caret-range-from-point.js | 7 +- packages/i18n/src/sprintf.js | 5 +- packages/lazy-import/tsconfig.json | 3 +- .../lib/tasks/add-milestone/index.js | 9 +- .../index.js | 8 +- 16 files changed, 104 insertions(+), 95 deletions(-) diff --git a/bin/plugin/commands/changelog.js b/bin/plugin/commands/changelog.js index 73d8afdae4edf1..dca2ce62406e7a 100644 --- a/bin/plugin/commands/changelog.js +++ b/bin/plugin/commands/changelog.js @@ -780,7 +780,9 @@ async function createChangelog( settings ) { try { changelog = await getChangelog( settings ); } catch ( error ) { - changelog = formats.error( error.stack ); + if ( error instanceof Error ) { + changelog = formats.error( error.stack ); + } } log( changelog ); diff --git a/package-lock.json b/package-lock.json index c3c1f113627cdb..691657bafb2674 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16403,16 +16403,13 @@ } }, "@types/classnames": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.10.tgz", - "integrity": "sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ==", - "dev": true - }, - "@types/clipboard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/clipboard/-/clipboard-2.0.1.tgz", - "integrity": "sha512-gJJX9Jjdt3bIAePQRRjYWG20dIhAgEqonguyHxXuqALxsoDsDLimihqrSg8fXgVTJ4KZCzkfglKtwsh/8dLfbA==", - "dev": true + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A==", + "dev": true, + "requires": { + "classnames": "*" + } }, "@types/color-convert": { "version": "2.0.0", @@ -16430,9 +16427,9 @@ "dev": true }, "@types/eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-hqzmggoxkOubpgTdcOltkfc5N8IftRJqU70d1jbOISjjZVPvjcr+CLi2CI70hx1SUIRkLgpglTy9w28nGe2Hsw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz", + "integrity": "sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==", "dev": true, "requires": { "@types/estree": "*", @@ -16450,9 +16447,9 @@ } }, "@types/estree": { - "version": "0.0.44", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.44.tgz", - "integrity": "sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==", + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", "dev": true }, "@types/events": { @@ -16501,9 +16498,9 @@ } }, "@types/highlight-words-core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/highlight-words-core/-/highlight-words-core-1.2.0.tgz", - "integrity": "sha512-yy+e7t3P5ABzT/Bl0Wy0hxworXGKKSJVQljaUQxco9ddXY5OZVbRm+yyzZAPBhP4C7KwfkZRRhNOCYkLbloFYw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/highlight-words-core/-/highlight-words-core-1.2.1.tgz", + "integrity": "sha512-9VZUA5omXBfn+hDxFjUDu1FOJTBM3LmvqfDey+Z6Aa8B8/JmF5SMj6FBrjfgJ/Q3YXOZd3qyTDfJyMZSs/wCUA==", "dev": true }, "@types/html-minifier-terser": { @@ -16575,9 +16572,9 @@ } }, "@types/lodash": { - "version": "4.14.149", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", - "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==" + "version": "4.14.172", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.172.tgz", + "integrity": "sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==" }, "@types/markdown-to-jsx": { "version": "6.11.3", @@ -16667,9 +16664,9 @@ "dev": true }, "@types/npm-package-arg": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/npm-package-arg/-/npm-package-arg-6.1.0.tgz", - "integrity": "sha512-vbt5fb0y1svMhu++1lwtKmZL76d0uPChFlw7kEzyUmTwfmpHRcFb8i0R8ElT69q/L+QLgK2hgECivIAvaEDwag==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-452/1Kp9IdM/oR10AyqAgZOxUt7eLbm+EMJ194L6oarMYdZNiFIFAOJ7IIr0OrZXTySgfHjJezh2oiyk2kc3ag==", "dev": true }, "@types/npmlog": { @@ -16696,9 +16693,9 @@ "dev": true }, "@types/prettier": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.0.tgz", - "integrity": "sha512-gDE8JJEygpay7IjA/u3JiIURvwZW08f0cZSZLAzFoX/ZmeqvS0Sqv+97aKuHpNsalAMMhwPe+iAS6fQbfmbt7A==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==", "dev": true }, "@types/pretty-hrtime": { @@ -16719,9 +16716,9 @@ "dev": true }, "@types/qs": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz", - "integrity": "sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==", + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true }, "@types/reach__router": { @@ -16767,9 +16764,9 @@ } }, "@types/requestidlecallback": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@types/requestidlecallback/-/requestidlecallback-0.3.1.tgz", - "integrity": "sha512-BnnRkgWYijCIndUn+LgoqKHX/hNpJC5G03B9y7mZya/C2gUQTSn75fEj3ZP1/Rl2E6EYeXh2/7/8UNEZ4X7HuQ==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@types/requestidlecallback/-/requestidlecallback-0.3.4.tgz", + "integrity": "sha512-aTSyiZuRemRLTQkJPb25L7A4/eR2Teo5l4yJ1V6P3+MFxEZckTDkNKNtr/V1zEOMzS6H8DgxF22U6jPAPrzQvw==", "dev": true }, "@types/responselike": { @@ -16782,13 +16779,10 @@ } }, "@types/semver": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.2.0.tgz", - "integrity": "sha512-TbB0A8ACUWZt3Y6bQPstW9QNbhNeebdgLX4T/ZfkrswAfUzRiXrgd9seol+X379Wa589Pu4UEx9Uok0D4RjRCQ==", - "dev": true, - "requires": { - "@types/node": "*" - } + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-D/2EJvAlCEtYFEYmmlGwbGXuK886HzyCc3nZX/tkFTQdEU8jZDAgiv08P162yB17y4ZXZoq7yFAnW4GDBb9Now==", + "dev": true }, "@types/source-list-map": { "version": "0.1.2", @@ -16818,9 +16812,9 @@ } }, "@types/tinycolor2": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.2.tgz", - "integrity": "sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.3.tgz", + "integrity": "sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==", "dev": true }, "@types/uglify-js": { @@ -16847,9 +16841,9 @@ "dev": true }, "@types/uuid": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", - "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", "dev": true }, "@types/vfile": { @@ -18182,7 +18176,7 @@ "version": "file:packages/compose", "requires": { "@babel/runtime": "^7.13.10", - "@types/lodash": "4.14.149", + "@types/lodash": "^4.14.172", "@types/mousetrap": "^1.6.8", "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/dom": "file:packages/dom", @@ -18190,7 +18184,7 @@ "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/priority-queue": "file:packages/priority-queue", - "clipboard": "^2.0.1", + "clipboard": "^2.0.8", "lodash": "^4.17.21", "mousetrap": "^1.6.5", "react-resize-aware": "^3.1.0", @@ -30500,9 +30494,9 @@ "dev": true }, "clipboard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.1.tgz", - "integrity": "sha512-7yhQBmtN+uYZmfRjjVjKa0dZdWuabzpSKGtyQZN+9C8xlC788SSJjOHWh7tzurfwTqTD5UDYAhIv5fRJg3sHjQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz", + "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==", "requires": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -59097,9 +59091,9 @@ "dev": true }, "tiny-emitter": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", - "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, "tiny-lr": { "version": "1.1.1", @@ -59449,9 +59443,9 @@ } }, "typescript": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", - "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz", + "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", "dev": true }, "ua-parser-js": { diff --git a/package.json b/package.json index 13696a49f7d48c..6b1d957ec8b053 100644 --- a/package.json +++ b/package.json @@ -106,20 +106,19 @@ "@testing-library/jest-dom": "5.11.9", "@testing-library/react": "11.2.2", "@testing-library/react-native": "7.1.0", - "@types/classnames": "2.2.10", - "@types/clipboard": "2.0.1", - "@types/eslint": "6.8.0", - "@types/estree": "0.0.44", - "@types/highlight-words-core": "1.2.0", - "@types/lodash": "4.14.149", - "@types/npm-package-arg": "6.1.0", - "@types/prettier": "1.19.0", - "@types/qs": "6.9.1", - "@types/requestidlecallback": "0.3.1", - "@types/semver": "7.2.0", + "@types/classnames": "2.3.1", + "@types/eslint": "7.28.0", + "@types/estree": "0.0.50", + "@types/highlight-words-core": "1.2.1", + "@types/lodash": "4.14.172", + "@types/npm-package-arg": "6.1.1", + "@types/prettier": "2.3.2", + "@types/qs": "6.9.7", + "@types/requestidlecallback": "0.3.4", + "@types/semver": "7.3.8", "@types/sprintf-js": "1.1.2", - "@types/tinycolor2": "1.4.2", - "@types/uuid": "8.3.0", + "@types/tinycolor2": "1.4.3", + "@types/uuid": "8.3.1", "@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma", "@wordpress/babel-plugin-makepot": "file:packages/babel-plugin-makepot", "@wordpress/babel-preset-default": "file:packages/babel-preset-default", @@ -209,7 +208,7 @@ "sprintf-js": "1.1.1", "style-loader": "3.2.1", "terser-webpack-plugin": "5.1.4", - "typescript": "4.1.3", + "typescript": "4.4.2", "uglify-js": "3.13.7", "uuid": "8.3.0", "wd": "1.12.1", diff --git a/packages/components/src/input-control/reducer/actions.ts b/packages/components/src/input-control/reducer/actions.ts index 652e7211e251e4..66ca918de77070 100644 --- a/packages/components/src/input-control/reducer/actions.ts +++ b/packages/components/src/input-control/reducer/actions.ts @@ -44,10 +44,7 @@ export type DragEndAction = Action< typeof DRAG_END, DragProps >; export type DragAction = Action< typeof DRAG, DragProps >; export type ResetAction = Action< typeof RESET, Partial< ValuePayload > >; export type UpdateAction = Action< typeof UPDATE, ValuePayload >; -export type InvalidateAction = Action< - typeof INVALIDATE, - { error: Error | null } ->; +export type InvalidateAction = Action< typeof INVALIDATE, { error: unknown } >; export type ChangeEventAction = | ChangeAction diff --git a/packages/components/src/input-control/reducer/reducer.ts b/packages/components/src/input-control/reducer/reducer.ts index 1aad3f8d3e6725..362739e758a42b 100644 --- a/packages/components/src/input-control/reducer/reducer.ts +++ b/packages/components/src/input-control/reducer/reducer.ts @@ -213,7 +213,7 @@ export function useInputControlStateReducer( * Actions for the reducer */ const change = createChangeEvent( actions.CHANGE ); - const invalidate = ( error: Error, event: SyntheticEvent ) => + const invalidate = ( error: unknown, event: SyntheticEvent ) => dispatch( { type: actions.INVALIDATE, payload: { error, event } } ); const reset = createChangeEvent( actions.RESET ); const commit = createChangeEvent( actions.COMMIT ); diff --git a/packages/components/src/input-control/reducer/state.ts b/packages/components/src/input-control/reducer/state.ts index 9fcbfad2d1ae28..455cce5f00f1da 100644 --- a/packages/components/src/input-control/reducer/state.ts +++ b/packages/components/src/input-control/reducer/state.ts @@ -11,7 +11,7 @@ import type { InputAction } from './actions'; export interface InputState { _event: Event | {}; - error: Error | null; + error: unknown; initialValue?: string; isDirty: boolean; isDragEnabled: boolean; diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 77809950efbdd5..a0a0e879a3066f 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -3,7 +3,10 @@ "compilerOptions": { "rootDir": "src", "declarationDir": "build-types", - "types": [ "gutenberg-env" ] + "types": [ "gutenberg-env" ], + // Some errors in Reakit types with TypeScript 4.3 + // Remove the following line when they've been addressed. + "skipLibCheck": true }, "references": [ { "path": "../compose" }, diff --git a/packages/compose/README.md b/packages/compose/README.md index c66787eb8c3d77..8d918eba025707 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -207,7 +207,7 @@ _Parameters_ _Returns_ -- `TFunc & import('lodash').Cancelable`: Debounced function. +- `import('lodash').DebouncedFunc`: Debounced function. ### useFocusOnMount @@ -452,7 +452,7 @@ _Parameters_ _Returns_ -- `TFunc & import('lodash').Cancelable`: Throttled function. +- `import('lodash').DebouncedFunc`: Throttled function. ### useViewportMatch diff --git a/packages/compose/package.json b/packages/compose/package.json index f97c1131b59c47..d1c4efc6294777 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -30,7 +30,7 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "^7.13.10", - "@types/lodash": "4.14.149", + "@types/lodash": "^4.14.172", "@types/mousetrap": "^1.6.8", "@wordpress/deprecated": "file:../deprecated", "@wordpress/dom": "file:../dom", @@ -38,7 +38,7 @@ "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/keycodes": "file:../keycodes", "@wordpress/priority-queue": "file:../priority-queue", - "clipboard": "^2.0.1", + "clipboard": "^2.0.8", "lodash": "^4.17.21", "mousetrap": "^1.6.5", "react-resize-aware": "^3.1.0", diff --git a/packages/compose/src/hooks/use-debounce/index.js b/packages/compose/src/hooks/use-debounce/index.js index 374f20a521a5e9..db66e74d590c65 100644 --- a/packages/compose/src/hooks/use-debounce/index.js +++ b/packages/compose/src/hooks/use-debounce/index.js @@ -23,7 +23,7 @@ import { useEffect } from '@wordpress/element'; * @param {TFunc} fn The function to debounce. * @param {number} [wait] The number of milliseconds to delay. * @param {import('lodash').DebounceSettings} [options] The options object. - * @return {TFunc & import('lodash').Cancelable} Debounced function. + * @return {import('lodash').DebouncedFunc} Debounced function. */ export default function useDebounce( fn, wait, options ) { /* eslint-enable jsdoc/valid-types */ diff --git a/packages/compose/src/hooks/use-throttle/index.js b/packages/compose/src/hooks/use-throttle/index.js index 8ad9589d112dec..45fb2696673ae1 100644 --- a/packages/compose/src/hooks/use-throttle/index.js +++ b/packages/compose/src/hooks/use-throttle/index.js @@ -22,7 +22,7 @@ import { useEffect } from '@wordpress/element'; * @param {TFunc} fn The function to throttle. * @param {number} [wait] The number of milliseconds to throttle invocations to. * @param {import('lodash').ThrottleSettings} [options] The options object. See linked documentation for details. - * @return {TFunc & import('lodash').Cancelable} Throttled function. + * @return {import('lodash').DebouncedFunc} Throttled function. */ export default function useThrottle( fn, wait, options ) { const throttled = useMemoOne( () => throttle( fn, wait, options ), [ diff --git a/packages/dom/src/dom/caret-range-from-point.js b/packages/dom/src/dom/caret-range-from-point.js index fb32c15f0f9759..a3f83926f083a2 100644 --- a/packages/dom/src/dom/caret-range-from-point.js +++ b/packages/dom/src/dom/caret-range-from-point.js @@ -4,7 +4,7 @@ * * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/caretRangeFromPoint * - * @param {Document} doc The document of the range. + * @param {DocumentMaybeWithCaretPositionFromPoint} doc The document of the range. * @param {number} x Horizontal position within the current viewport. * @param {number} y Vertical position within the current viewport. * @@ -34,3 +34,8 @@ export default function caretRangeFromPoint( doc, x, y ) { return range; } + +/** + * @typedef {{caretPositionFromPoint?: (x: number, y: number)=> CaretPosition | null} & Document } DocumentMaybeWithCaretPositionFromPoint + * @typedef {{ readonly offset: number; readonly offsetNode: Node; getClientRect(): DOMRect | null; }} CaretPosition + */ diff --git a/packages/i18n/src/sprintf.js b/packages/i18n/src/sprintf.js index 98c4bade268a11..550248a81c9e9e 100644 --- a/packages/i18n/src/sprintf.js +++ b/packages/i18n/src/sprintf.js @@ -28,8 +28,9 @@ export function sprintf( format, ...args ) { try { return sprintfjs.sprintf( format, ...args ); } catch ( error ) { - logErrorOnce( 'sprintf error: \n\n' + error.toString() ); - + if ( error instanceof Error ) { + logErrorOnce( 'sprintf error: \n\n' + error.toString() ); + } return format; } } diff --git a/packages/lazy-import/tsconfig.json b/packages/lazy-import/tsconfig.json index 426ab13d0aa8f6..fd6069e3843aed 100644 --- a/packages/lazy-import/tsconfig.json +++ b/packages/lazy-import/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "lib", - "declarationDir": "build-types" + "declarationDir": "build-types", + "useUnknownInCatchVariables": false }, "include": [ "lib/**/*" ] } diff --git a/packages/project-management-automation/lib/tasks/add-milestone/index.js b/packages/project-management-automation/lib/tasks/add-milestone/index.js index 3f26cd3ed3f31f..1058f6bc9d7a71 100644 --- a/packages/project-management-automation/lib/tasks/add-milestone/index.js +++ b/packages/project-management-automation/lib/tasks/add-milestone/index.js @@ -19,7 +19,7 @@ const DAYS_PER_RELEASE = 14; * Returns true if the given error object represents a duplicate entry error, or * false otherwise. * - * @param {RequestError} requestError Error to test. + * @param {unknown} requestError Error to test. * * @return {boolean} Whether error is a duplicate validation request error. */ @@ -27,7 +27,12 @@ const isDuplicateValidationError = ( requestError ) => { // The included version of RequestError provides no way to access the // full 'errors' array that the github REST API returns. Hopefully they // resolve this soon! - return requestError.message.includes( 'already_exists' ); + const errorMessage = /** @type {undefined | null | {message?: string}} */ ( requestError ) + ?.message; + return ( + typeof errorMessage === 'string' && + errorMessage.includes( 'already_exists' ) + ); }; /** diff --git a/packages/project-management-automation/lib/tasks/first-time-contributor-account-link/index.js b/packages/project-management-automation/lib/tasks/first-time-contributor-account-link/index.js index 9d18ccad467aa8..b60f65d5f5dc05 100644 --- a/packages/project-management-automation/lib/tasks/first-time-contributor-account-link/index.js +++ b/packages/project-management-automation/lib/tasks/first-time-contributor-account-link/index.js @@ -86,9 +86,11 @@ async function firstTimeContributorAccountLink( payload, octokit ) { try { hasProfile = await hasWordPressProfile( author ); } catch ( error ) { - debug( - `first-time-contributor-account-link: Error retrieving from profile API:\n\n${ error.toString() }` - ); + if ( error instanceof Object ) { + debug( + `first-time-contributor-account-link: Error retrieving from profile API:\n\n${ error.toString() }` + ); + } return; } From e834828230e7933450418b92f5342f854a675715 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Wed, 1 Sep 2021 12:02:59 +0200 Subject: [PATCH 097/214] Testing Overview: update JS example to use React Testing Library (#34423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Haz Co-authored-by: Greg Ziółkowski --- docs/contributors/code/testing-overview.md | 49 +++++++++++++++------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/docs/contributors/code/testing-overview.md b/docs/contributors/code/testing-overview.md index 98827115b62580..5f288d618233f2 100644 --- a/docs/contributors/code/testing-overview.md +++ b/docs/contributors/code/testing-overview.md @@ -21,7 +21,7 @@ When writing tests consider the following: Tests for JavaScript use [Jest](https://jestjs.io/) as the test runner and its API for [globals](https://jestjs.io/docs/en/api.html) (`describe`, `test`, `beforeEach` and so on) [assertions](https://jestjs.io/docs/en/expect.html), [mocks](https://jestjs.io/docs/en/mock-functions.html), [spies](https://jestjs.io/docs/en/jest-object.html#jestspyonobject-methodname) and [mock functions](https://jestjs.io/docs/en/mock-function-api.html). If needed, you can also use [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) for React component testing. -It should be noted that in the past, React components were unit tested with [Enzyme](https://github.com/airbnb/enzyme). However, for new tests, it is preferred to use React Testing Library (RTL) and over time old tests should be refactored to use RTL too (typically when working on code that touches an old test). +_It should be noted that in the past, React components were unit tested with [Enzyme](https://github.com/airbnb/enzyme). However, React Testing Library (RTL) should be used for new tests instead, and over time old tests should be refactored to use RTL too (typically when working on code that touches an old test)._ Assuming you've followed the [instructions](/docs/contributors/code/getting-started-with-code-contribution.md) to install Node and project dependencies, tests can be run from the command-line with NPM: @@ -273,23 +273,22 @@ You should never create or modify a snapshot directly, they are generated and up Snapshot are mostly targeted at component testing. They make us conscious of changes to a component's structure which makes them _ideal_ for refactoring. If a snapshot is kept up to date over the course of a series of commits, the snapshot diffs record the evolution of a component's structure. Pretty cool 😎 -```js -import { shallow } from 'enzyme'; +```jsx +import { render, screen } from '@testing-library/react'; import SolarSystem from 'solar-system'; -import { Mars } from 'planets'; describe( 'SolarSystem', () => { test( 'should render', () => { - const wrapper = shallow( ); + const { container } = render( ); - expect( wrapper ).toMatchSnapshot(); + expect( container.firstChild ).toMatchSnapshot(); } ); test( 'should contain mars if planets is true', () => { - const wrapper = shallow( ); + const { container } = render( ); - expect( wrapper ).toMatchSnapshot(); - expect( wrapper.find( Mars ) ).toHaveLength( 1 ); + expect( container.firstChild ).toMatchSnapshot(); + expect( screen.getByText( /mars/i ) ).toBeInTheDocument(); } ); } ); ``` @@ -332,23 +331,41 @@ If you're starting a refactor, snapshots are quite nice, you can add them as the Snapshots themselves don't express anything about what we expect. Snapshots are best used in conjunction with other tests that describe our expectations, like in the example above: -```js +```jsx test( 'should contain mars if planets is true', () => { - const wrapper = shallow( ); + const { container } = render( ); // Snapshot will catch unintended changes - expect( wrapper ).toMatchSnapshot(); + expect( container.firstChild ).toMatchSnapshot(); // This is what we actually expect to find in our test - expect( wrapper.find( Mars ) ).toHaveLength( 1 ); + expect( screen.getByText( /mars/i ) ).toBeInTheDocument(); } ); ``` -[`shallow`](http://airbnb.io/enzyme/docs/api/shallow.html) rendering is your friend: +Another good technique is to use the `toMatchDiffSnapshot` function (provided by the [`snapshot-diff` package](https://github.com/jest-community/snapshot-diff)), which allows to snapshot only the difference between two different states of the DOM. This approach is useful to test the effects of a prop change on the resulting DOM while generating a much smaller snapshot, like in this example: -> Shallow rendering is useful to constrain yourself to testing a component as a unit, and to ensure that your tests aren't indirectly asserting on behavior of child components. +```jsx +test( 'should render a darker background when isShady is true', () => { + const { container } = render( Body ); + const { container: containerShady } = render( + Body + ); + expect( container ).toMatchDiffSnapshot( containerShady ); +} ); +``` -It's tempting to snapshot deep renders, but that makes for huge snapshots. Additionally, deep renders no longer test a single component, but an entire tree. With `shallow`, we snapshot just the components that are directly rendered by the component we want to test. +Similarly, the `toMatchStyleDiffSnapshot` function allows to snapshot only the difference between the _styles_ associated to two different states of a component, like in this example: + +```jsx +test( 'should render margin', () => { + const { container: spacer } = render( ); + const { container: spacerWithMargin } = render( ); + expect( spacerWithMargin.firstChild ).toMatchStyleDiffSnapshot( + spacer.firstChild + ); +} ); +``` #### Troubleshooting From 6730182e37287c871b01897b484c4615cef8a3c6 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Wed, 1 Sep 2021 12:38:46 +0200 Subject: [PATCH 098/214] Chore: Fix the missing syntax issue in `@wordpress/project-management-automation` Follow-up for #34373. --- .../lib/tasks/add-milestone/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/project-management-automation/lib/tasks/add-milestone/index.js b/packages/project-management-automation/lib/tasks/add-milestone/index.js index 1058f6bc9d7a71..f70a6560bea996 100644 --- a/packages/project-management-automation/lib/tasks/add-milestone/index.js +++ b/packages/project-management-automation/lib/tasks/add-milestone/index.js @@ -27,8 +27,10 @@ const isDuplicateValidationError = ( requestError ) => { // The included version of RequestError provides no way to access the // full 'errors' array that the github REST API returns. Hopefully they // resolve this soon! - const errorMessage = /** @type {undefined | null | {message?: string}} */ ( requestError ) - ?.message; + const errorMessage = + requestError && + typeof requestError === 'object' && + /** @type {{message?: string}} */ ( requestError ).message; return ( typeof errorMessage === 'string' && errorMessage.includes( 'already_exists' ) From 796d25c34cdf26d1dd788eb174b18a1b282770d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Wed, 1 Sep 2021 12:49:18 +0200 Subject: [PATCH 099/214] Blocks: Register block when invalid value provided for the icon (#34350) * Blocks: Register block when invalid value provided for the icon * Include an entry in the package changelog --- package-lock.json | 1 - packages/blocks/CHANGELOG.md | 4 ++ packages/blocks/package.json | 1 - packages/blocks/src/api/constants.js | 2 + packages/blocks/src/api/registration.js | 5 +- packages/blocks/src/api/test/registration.js | 57 ++++++-------------- packages/blocks/src/api/utils.js | 2 + 7 files changed, 25 insertions(+), 47 deletions(-) diff --git a/package-lock.json b/package-lock.json index 691657bafb2674..d4899cfca2fa11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18106,7 +18106,6 @@ "@wordpress/hooks": "file:packages/hooks", "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", - "@wordpress/icons": "file:packages/icons", "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/shortcode": "file:packages/shortcode", "hpq": "^1.3.0", diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index 79f2d8aaee2714..b51c240a069ea1 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Backward Compatibility + +- Register a block even when an invalid value provided for the icon setting ([#34350](https://github.com/WordPress/gutenberg/pull/34350)). + ## 11.0.0 (2021-07-29) ### Breaking Change diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 8741fb317c907e..e7c759dbe59137 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -40,7 +40,6 @@ "@wordpress/hooks": "file:../hooks", "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", - "@wordpress/icons": "file:../icons", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/shortcode": "file:../shortcode", "hpq": "^1.3.0", diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index d7ce7cafd25261..5564b339ec4a41 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -1,3 +1,5 @@ +export const BLOCK_ICON_DEFAULT = 'block-default'; + /** * Array of valid keys in a block type settings deprecation object. * diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index bd400e148a6ebe..09033b2ef4beb8 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -25,14 +25,13 @@ import { import { applyFilters } from '@wordpress/hooks'; import { select, dispatch } from '@wordpress/data'; import { _x } from '@wordpress/i18n'; -import { blockDefault } from '@wordpress/icons'; /** * Internal dependencies */ import i18nBlockSchema from './i18n-block.json'; import { isValidIcon, normalizeIconObject } from './utils'; -import { DEPRECATED_ENTRY_KEYS } from './constants'; +import { BLOCK_ICON_DEFAULT, DEPRECATED_ENTRY_KEYS } from './constants'; import { store as blocksStore } from '../store'; /** @@ -265,7 +264,7 @@ export function registerBlockType( blockNameOrMetadata, settings ) { settings = { name, - icon: blockDefault, + icon: BLOCK_ICON_DEFAULT, keywords: [], attributes: {}, providesContext: {}, diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 49851544dd5ae2..4b3d2864a0b229 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -10,7 +10,6 @@ import { noop, get, omit, pick } from 'lodash'; */ import { addFilter, removeAllFilters, removeFilter } from '@wordpress/hooks'; import { select } from '@wordpress/data'; -import { blockDefault as blockIcon } from '@wordpress/icons'; /** * Internal dependencies @@ -36,7 +35,7 @@ import { serverSideBlockDefinitions, unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase } from '../registration'; -import { DEPRECATED_ENTRY_KEYS } from '../constants'; +import { BLOCK_ICON_DEFAULT, DEPRECATED_ENTRY_KEYS } from '../constants'; import { store as blocksStore } from '../../store'; describe( 'blocks', () => { @@ -121,9 +120,7 @@ describe( 'blocks', () => { expect( console ).not.toHaveErrored(); expect( block ).toEqual( { name: 'my-plugin/fancy-block-4', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: {}, providesContext: {}, usesContext: [], @@ -268,9 +265,7 @@ describe( 'blocks', () => { name: 'core/test-block-with-defaults', title: 'block title', category: 'text', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: {}, providesContext: {}, usesContext: [], @@ -301,9 +296,7 @@ describe( 'blocks', () => { save: noop, category: 'text', title: 'block title', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: { ok: { type: 'boolean', @@ -338,9 +331,7 @@ describe( 'blocks', () => { name: blockName, save: expect.any( Function ), title: 'block title', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: {}, providesContext: {}, usesContext: [], @@ -371,9 +362,7 @@ describe( 'blocks', () => { name: blockName, save: expect.any( Function ), title: 'block title', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: {}, providesContext: { fontSize: 'fontSize', @@ -410,9 +399,7 @@ describe( 'blocks', () => { save: expect.any( Function ), title: 'block title', category: 'widgets', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: {}, providesContext: {}, usesContext: [], @@ -636,9 +623,7 @@ describe( 'blocks', () => { save: noop, category: 'text', title: 'block title', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: {}, providesContext: {}, usesContext: [], @@ -723,7 +708,7 @@ describe( 'blocks', () => { ...omit( { name, - icon: blockIcon, + icon: BLOCK_ICON_DEFAULT, attributes: {}, providesContext: {}, usesContext: [], @@ -960,9 +945,7 @@ describe( 'blocks', () => { save: noop, category: 'text', title: 'block title', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: {}, providesContext: {}, usesContext: [], @@ -979,9 +962,7 @@ describe( 'blocks', () => { save: noop, category: 'text', title: 'block title', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: {}, providesContext: {}, usesContext: [], @@ -1059,9 +1040,7 @@ describe( 'blocks', () => { save: noop, category: 'text', title: 'block title', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: {}, providesContext: {}, usesContext: [], @@ -1085,9 +1064,7 @@ describe( 'blocks', () => { save: noop, category: 'text', title: 'block title', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: {}, providesContext: {}, usesContext: [], @@ -1118,9 +1095,7 @@ describe( 'blocks', () => { save: noop, category: 'text', title: 'block title', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: {}, providesContext: {}, usesContext: [], @@ -1135,9 +1110,7 @@ describe( 'blocks', () => { save: noop, category: 'text', title: 'block title', - icon: { - src: blockIcon, - }, + icon: { src: BLOCK_ICON_DEFAULT }, attributes: {}, providesContext: {}, usesContext: [], diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index d6b588668b0edd..653676a85fd70a 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -14,6 +14,7 @@ import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; /** * Internal dependencies */ +import { BLOCK_ICON_DEFAULT } from './constants'; import { getBlockType, getDefaultBlockName } from './registration'; import { createBlock } from './factory'; @@ -89,6 +90,7 @@ export function isValidIcon( icon ) { * @return {WPBlockTypeIconDescriptor} Object describing the icon. */ export function normalizeIconObject( icon ) { + icon = icon || BLOCK_ICON_DEFAULT; if ( isValidIcon( icon ) ) { return { src: icon }; } From 7901895ca20cf61e402925e31571d659dab64721 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Wed, 1 Sep 2021 14:38:31 +0200 Subject: [PATCH 100/214] core-data: move locks state from store to local variable (#34374) * core-data: move locks state from store to local variable * Added unit test for the locks engine * Fix actions unit tests --- packages/core-data/src/actions.js | 28 +- packages/core-data/src/index.js | 13 +- packages/core-data/src/locks/actions.js | 68 +---- packages/core-data/src/locks/engine.js | 43 +++ packages/core-data/src/locks/index.js | 3 - packages/core-data/src/locks/reducer.js | 4 +- packages/core-data/src/locks/selectors.js | 8 +- packages/core-data/src/locks/test/actions.js | 280 ------------------ packages/core-data/src/locks/test/engine.js | 135 +++++++++ packages/core-data/src/locks/test/reducer.js | 2 +- .../core-data/src/locks/test/selectors.js | 229 +++++++------- packages/core-data/src/reducer.js | 2 - packages/core-data/src/resolvers.js | 26 +- packages/core-data/src/test/actions.js | 79 ++--- packages/core-data/src/test/resolvers.js | 31 +- 15 files changed, 373 insertions(+), 578 deletions(-) create mode 100644 packages/core-data/src/locks/engine.js delete mode 100644 packages/core-data/src/locks/index.js delete mode 100644 packages/core-data/src/locks/test/actions.js create mode 100644 packages/core-data/src/locks/test/engine.js diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index fcc41bf5dd6cbf..49c624977e48dc 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -17,10 +17,6 @@ import { addQueryArgs } from '@wordpress/url'; */ import { receiveItems, removeItems, receiveQueriedItems } from './queried-data'; import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; -import { - __unstableAcquireStoreLock, - __unstableReleaseStoreLock, -} from './locks'; import { createBatch } from './batch'; import { getDispatch } from './controls'; import { STORE_NAME } from './name'; @@ -183,11 +179,14 @@ export function* deleteEntityRecord( return; } - const lock = yield* __unstableAcquireStoreLock( + const lock = yield controls.dispatch( + STORE_NAME, + '__unstableAcquireStoreLock', STORE_NAME, [ 'entities', 'data', kind, name, recordId ], { exclusive: true } ); + try { yield { type: 'DELETE_ENTITY_RECORD_START', @@ -230,7 +229,11 @@ export function* deleteEntityRecord( return deletedRecord; } finally { - yield* __unstableReleaseStoreLock( lock ); + yield controls.dispatch( + STORE_NAME, + '__unstableReleaseStoreLock', + lock + ); } } @@ -374,13 +377,12 @@ export const saveEntityRecord = ( const entityIdKey = entity.key || DEFAULT_ENTITY_KEY; const recordId = record[ entityIdKey ]; - const lock = await dispatch( - __unstableAcquireStoreLock( - STORE_NAME, - [ 'entities', 'data', kind, name, recordId || uuid() ], - { exclusive: true } - ) + const lock = await dispatch.__unstableAcquireStoreLock( + STORE_NAME, + [ 'entities', 'data', kind, name, recordId || uuid() ], + { exclusive: true } ); + try { // Evaluate optimized edits. // (Function edits that should be evaluated on save to avoid expensive computations on every edit.) @@ -558,7 +560,7 @@ export const saveEntityRecord = ( return updatedRecord; } finally { - await dispatch( __unstableReleaseStoreLock( lock ) ); + await dispatch.__unstableReleaseStoreLock( lock ); } }; diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index 43b9cadb1bc775..bcff6e6c4f4dde 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -11,8 +11,7 @@ import reducer from './reducer'; import * as selectors from './selectors'; import * as actions from './actions'; import * as resolvers from './resolvers'; -import * as locksSelectors from './locks/selectors'; -import * as locksActions from './locks/actions'; +import createLocksActions from './locks/actions'; import customControls from './controls'; import { defaultEntities, getMethodName } from './entities'; import { STORE_NAME } from './name'; @@ -57,14 +56,14 @@ const entityActions = defaultEntities.reduce( ( result, entity ) => { return result; }, {} ); -const storeConfig = { +const storeConfig = () => ( { reducer, controls: { ...customControls, ...controls }, - actions: { ...actions, ...entityActions, ...locksActions }, - selectors: { ...selectors, ...entitySelectors, ...locksSelectors }, + actions: { ...actions, ...entityActions, ...createLocksActions() }, + selectors: { ...selectors, ...entitySelectors }, resolvers: { ...resolvers, ...entityResolvers }, __experimentalUseThunks: true, -}; +} ); /** * Store definition for the code data namespace. @@ -73,7 +72,7 @@ const storeConfig = { * * @type {Object} */ -export const store = createReduxStore( STORE_NAME, storeConfig ); +export const store = createReduxStore( STORE_NAME, storeConfig() ); register( store ); diff --git a/packages/core-data/src/locks/actions.js b/packages/core-data/src/locks/actions.js index 4dc6b324a29e68..b6bcb93a3dd92f 100644 --- a/packages/core-data/src/locks/actions.js +++ b/packages/core-data/src/locks/actions.js @@ -1,66 +1,18 @@ -/** - * WordPress dependencies - */ -import { __unstableAwaitPromise } from '@wordpress/data-controls'; -import { controls } from '@wordpress/data'; - /** * Internal dependencies */ -import { STORE_NAME } from '../name'; +import createLocks from './engine'; -export function* __unstableAcquireStoreLock( store, path, { exclusive } ) { - const promise = yield* __unstableEnqueueLockRequest( store, path, { - exclusive, - } ); - yield* __unstableProcessPendingLockRequests(); - return yield __unstableAwaitPromise( promise ); -} +export default function createLocksActions() { + const locks = createLocks(); -export function* __unstableEnqueueLockRequest( store, path, { exclusive } ) { - let notifyAcquired; - const promise = new Promise( ( resolve ) => { - notifyAcquired = resolve; - } ); - yield { - type: 'ENQUEUE_LOCK_REQUEST', - request: { store, path, exclusive, notifyAcquired }, - }; - return promise; -} - -export function* __unstableReleaseStoreLock( lock ) { - yield { - type: 'RELEASE_LOCK', - lock, - }; - yield* __unstableProcessPendingLockRequests(); -} + function __unstableAcquireStoreLock( store, path, { exclusive } ) { + return () => locks.acquire( store, path, exclusive ); + } -export function* __unstableProcessPendingLockRequests() { - const lockRequests = yield controls.select( - STORE_NAME, - '__unstableGetPendingLockRequests' - ); - for ( const request of lockRequests ) { - const { store, path, exclusive, notifyAcquired } = request; - const isAvailable = yield controls.select( - STORE_NAME, - '__unstableIsLockAvailable', - store, - path, - { - exclusive, - } - ); - if ( isAvailable ) { - const lock = { store, path, exclusive }; - yield { - type: 'GRANT_LOCK_REQUEST', - lock, - request, - }; - notifyAcquired( lock ); - } + function __unstableReleaseStoreLock( lock ) { + return () => locks.release( lock ); } + + return { __unstableAcquireStoreLock, __unstableReleaseStoreLock }; } diff --git a/packages/core-data/src/locks/engine.js b/packages/core-data/src/locks/engine.js new file mode 100644 index 00000000000000..a3e3ec4e77947f --- /dev/null +++ b/packages/core-data/src/locks/engine.js @@ -0,0 +1,43 @@ +/** + * Internal dependencies + */ +import reducer from './reducer'; +import { isLockAvailable, getPendingLockRequests } from './selectors'; + +export default function createLocks() { + let state = reducer( undefined, { type: '@@INIT' } ); + + function processPendingLockRequests() { + for ( const request of getPendingLockRequests( state ) ) { + const { store, path, exclusive, notifyAcquired } = request; + if ( isLockAvailable( state, store, path, { exclusive } ) ) { + const lock = { store, path, exclusive }; + state = reducer( state, { + type: 'GRANT_LOCK_REQUEST', + lock, + request, + } ); + notifyAcquired( lock ); + } + } + } + + function acquire( store, path, exclusive ) { + return new Promise( ( resolve ) => { + state = reducer( state, { + type: 'ENQUEUE_LOCK_REQUEST', + request: { store, path, exclusive, notifyAcquired: resolve }, + } ); + processPendingLockRequests(); + } ); + } + function release( lock ) { + state = reducer( state, { + type: 'RELEASE_LOCK', + lock, + } ); + processPendingLockRequests(); + } + + return { acquire, release }; +} diff --git a/packages/core-data/src/locks/index.js b/packages/core-data/src/locks/index.js deleted file mode 100644 index 57e124e445d87d..00000000000000 --- a/packages/core-data/src/locks/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from './actions'; -export * from './selectors'; -export { default as reducer } from './reducer'; diff --git a/packages/core-data/src/locks/reducer.js b/packages/core-data/src/locks/reducer.js index 878313b67b341f..11a2b899e1827a 100644 --- a/packages/core-data/src/locks/reducer.js +++ b/packages/core-data/src/locks/reducer.js @@ -19,7 +19,7 @@ const DEFAULT_STATE = { * * @return {Object} Updated state. */ -export function locks( state = DEFAULT_STATE, action ) { +export default function locks( state = DEFAULT_STATE, action ) { switch ( action.type ) { case 'ENQUEUE_LOCK_REQUEST': { const { request } = action; @@ -60,5 +60,3 @@ export function locks( state = DEFAULT_STATE, action ) { return state; } - -export default locks; diff --git a/packages/core-data/src/locks/selectors.js b/packages/core-data/src/locks/selectors.js index 233b36985a5c54..c5f0a42e722374 100644 --- a/packages/core-data/src/locks/selectors.js +++ b/packages/core-data/src/locks/selectors.js @@ -8,13 +8,13 @@ import { getNode, } from './utils'; -export function __unstableGetPendingLockRequests( state ) { - return state.locks.requests; +export function getPendingLockRequests( state ) { + return state.requests; } -export function __unstableIsLockAvailable( state, store, path, { exclusive } ) { +export function isLockAvailable( state, store, path, { exclusive } ) { const storePath = [ store, ...path ]; - const locks = state.locks.tree; + const locks = state.tree; // Validate all parents and the node itself for ( const node of iteratePath( locks, storePath ) ) { diff --git a/packages/core-data/src/locks/test/actions.js b/packages/core-data/src/locks/test/actions.js deleted file mode 100644 index 70f77c1dc05e9c..00000000000000 --- a/packages/core-data/src/locks/test/actions.js +++ /dev/null @@ -1,280 +0,0 @@ -/** - * Internal dependencies - */ -import { - __unstableAcquireStoreLock, - __unstableEnqueueLockRequest, - __unstableReleaseStoreLock, - __unstableProcessPendingLockRequests, -} from '../actions'; - -const store = 'test'; -const path = [ 'blue', 'bird' ]; - -describe( '__unstableEnqueueLockRequest', () => { - it( 'Enqueues a lock request', async () => { - const fulfillment = __unstableEnqueueLockRequest( store, path, { - exclusive: true, - } ); - - // Start - expect( fulfillment.next().value ).toMatchObject( { - type: 'ENQUEUE_LOCK_REQUEST', - request: { - store, - path, - exclusive: true, - notifyAcquired: expect.any( Function ), - }, - } ); - - // Should return a promise - expect( fulfillment.next() ).toMatchObject( { - done: true, - value: expect.any( Promise ), - } ); - } ); - - it( 'Returns a promise fulfilled only after calling notifyAcquired', async () => { - const fulfillment = __unstableEnqueueLockRequest( store, path, { - exclusive: true, - } ); - const { request } = fulfillment.next().value; - const promise = fulfillment.next().value; - const fulfilled = jest.fn(); - promise.then( fulfilled ); - // Fulfilled should not be called until notifyAcquired is called - await sleep( 1 ); - expect( fulfilled ).not.toBeCalled(); - const lock = {}; - request.notifyAcquired( lock ); - expect( fulfilled ).not.toBeCalled(); - - // Promises are resolved only the next tick, so let's wait a little: - await sleep( 1 ); - expect( fulfilled ).toBeCalledTimes( 1 ); - - // Calling notifyAcquired again shouldn't have any effect: - request.notifyAcquired( lock ); - await sleep( 1 ); - expect( fulfilled ).toBeCalledTimes( 1 ); - } ); -} ); - -describe( '__unstableProcessPendingLockRequests', () => { - const exclusive = true; - const lock = { store, path, exclusive }; - - let notifyAcquired; - let request; - - beforeEach( () => { - notifyAcquired = jest.fn(); - request = { store, path, exclusive, notifyAcquired }; - } ); - - it( 'Grants a lock request that may be granted', async () => { - const fulfillment = __unstableProcessPendingLockRequests(); - - // Get pending lock requests - expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); - - // Find one and check if the request may be granted - expect( fulfillment.next( [ request ] ).value.type ).toBe( - '@@data/SELECT' - ); - - // It may, grant it - expect( fulfillment.next( true ).value.type ).toBe( - 'GRANT_LOCK_REQUEST' - ); - - // Ensure the promise isn't fulfilled until after GRANT_LOCK_REQUEST finishes - expect( notifyAcquired ).not.toBeCalled(); - - // All requests processed, return - expect( fulfillment.next() ).toMatchObject( { - done: true, - value: undefined, - } ); - - // Ensure the promise is fulfilled once GRANT_LOCK_REQUEST finishes - expect( notifyAcquired ).toBeCalledWith( lock ); - expect( notifyAcquired ).toBeCalledTimes( 1 ); - - // All requests processed, return - expect( fulfillment.next() ).toMatchObject( { - done: true, - value: undefined, - } ); - } ); - - it( 'Does not grants a lock request that may not be granted', async () => { - const fulfillment = __unstableProcessPendingLockRequests(); - - // Get pending lock requests - expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); - - // Find one and check if the request may be granted - expect( fulfillment.next( [ request ] ).value.type ).toBe( - '@@data/SELECT' - ); - - // It may not, let's finish - expect( fulfillment.next( false ) ).toMatchObject( { - done: true, - value: undefined, - } ); - } ); - - it( 'Handles multiple lock requests', async () => { - const fulfillment = __unstableProcessPendingLockRequests(); - - // Get pending lock requests - expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); - - // Find one and check if the request may be granted - expect( fulfillment.next( [ request, request ] ).value.type ).toBe( - '@@data/SELECT' - ); - - // It may not, continue - check if the next one may be granted - expect( fulfillment.next( false ).value.type ).toBe( '@@data/SELECT' ); - // It may, grant it - expect( fulfillment.next( true ).value.type ).toBe( - 'GRANT_LOCK_REQUEST' - ); - - // Ensure the promise isn't fulfilled until after GRANT_LOCK_REQUEST finishes - expect( notifyAcquired ).not.toBeCalled(); - - // All requests processed, return - expect( fulfillment.next() ).toMatchObject( { - done: true, - value: undefined, - } ); - - // Ensure the promise is fulfilled once GRANT_LOCK_REQUEST finishes - expect( notifyAcquired ).toBeCalledWith( lock ); - expect( notifyAcquired ).toBeCalledTimes( 1 ); - - // All requests processed, return - expect( fulfillment.next() ).toMatchObject( { - done: true, - value: undefined, - } ); - } ); -} ); - -describe( '__unstableAcquireStoreLock', () => { - const exclusive = true; - const lock = { store, path, exclusive }; - - let notifyAcquired; - let request; - - beforeEach( () => { - notifyAcquired = jest.fn(); - request = { store, path, exclusive, notifyAcquired }; - } ); - - it( 'Enqueues a lock request and attempts to fulfill it', async () => { - const fulfillment = __unstableAcquireStoreLock( store, path, { - exclusive, - } ); - - // Start - expect( fulfillment.next().value.type ).toBe( 'ENQUEUE_LOCK_REQUEST' ); - - expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); - - // Check if lock may be granted - expect( fulfillment.next( [ request ] ).value.type ).toBe( - '@@data/SELECT' - ); - - // Grant lock request - expect( fulfillment.next( true ).value.type ).toBe( - 'GRANT_LOCK_REQUEST' - ); - - // Ensure the promise isn't fulfilled until after GRANT_LOCK_REQUEST finishes - expect( notifyAcquired ).not.toBeCalled(); - - // Await for lock promise fulfillment - expect( fulfillment.next( 'promise' ).value.type ).toBe( - 'AWAIT_PROMISE' - ); - - // Ensure the promise is fulfilled once GRANT_LOCK_REQUEST finishes - expect( notifyAcquired ).toBeCalledWith( lock ); - - // Return lock - expect( fulfillment.next( lock ) ).toMatchObject( { - done: true, - value: lock, - } ); - } ); - - it( 'Enqueues a lock request and waits until fultillment it when not available', async () => { - const fulfillment = __unstableAcquireStoreLock( store, path, { - exclusive, - } ); - - // Start - expect( fulfillment.next().value.type ).toBe( 'ENQUEUE_LOCK_REQUEST' ); - - expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); - - // Check if lock may be granted - expect( fulfillment.next( [ request ] ).value.type ).toBe( - '@@data/SELECT' - ); - - // Await until lock request is granted - expect( fulfillment.next( false ).value.type ).toBe( 'AWAIT_PROMISE' ); - - // Ensure the promise isn't fulfilled at this point... - expect( notifyAcquired ).not.toBeCalled(); - - await sleep( 1000 ); - - // ...or even a second lateer - expect( notifyAcquired ).not.toBeCalled(); - - // Let's assume the promise was fulfilled in the end, the action should return - // the lock once that happens. - expect( fulfillment.next( lock ) ).toMatchObject( { - done: true, - value: lock, - } ); - } ); -} ); - -describe( '__unstableReleaseStoreLock', () => { - const lock = { store, path, exclusive: true }; - - it( 'Releases a lock request and attempts to fulfill pending lock requests', async () => { - const fulfillment = __unstableReleaseStoreLock( lock ); - - // Start - expect( fulfillment.next().value ).toMatchObject( { - type: 'RELEASE_LOCK', - lock, - } ); - - expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); - - // Short-circuit with no results and return - expect( fulfillment.next( [] ) ).toMatchObject( { - done: true, - value: undefined, - } ); - } ); -} ); - -const sleep = ( ms ) => { - const promise = new Promise( ( resolve ) => setTimeout( resolve, ms ) ); - jest.advanceTimersByTime( ms + 1 ); - return promise; -}; diff --git a/packages/core-data/src/locks/test/engine.js b/packages/core-data/src/locks/test/engine.js new file mode 100644 index 00000000000000..0c629a3461e132 --- /dev/null +++ b/packages/core-data/src/locks/test/engine.js @@ -0,0 +1,135 @@ +/** + * Internal dependencies + */ +import createLocks from '../engine'; + +jest.useRealTimers(); + +// we correctly await all promises with expect calls, but the rule doesn't detect that +/* eslint-disable jest/valid-expect-in-promise */ + +describe( 'Locks engine', () => { + it( 'does not grant two exclusive locks at once', async () => { + const locks = createLocks(); + + let l1Granted = false; + let l2Granted = false; + + // request two locks + const l1 = locks.acquire( 'store', [ 'root' ], true ); + const l2 = locks.acquire( 'store', [ 'root' ], true ); + + // on each grant, verify that the other lock is not granted at the same time + const check1 = l1.then( () => { + l1Granted = true; + expect( l2Granted ).toBe( false ); + } ); + + const check2 = l2.then( () => { + l2Granted = true; + expect( l1Granted ).toBe( false ); + } ); + + // unlock both + const lock1 = await l1; + locks.release( lock1 ); + l1Granted = false; + + const lock2 = await l2; + locks.release( lock2 ); + l2Granted = false; + + // ensure that both locks were granted and checked + return await Promise.all( [ check1, check2 ] ); + } ); + + it( 'does not grant an exclusive lock if a non-exclusive one already exists', async () => { + const locks = createLocks(); + + let l1Granted = false; + let l2Granted = false; + + // request two locks + const l1 = locks.acquire( 'store', [ 'root' ], false ); + const l2 = locks.acquire( 'store', [ 'root' ], true ); + + // on each grant, verify that the other lock is not granted at the same time + const check1 = l1.then( () => { + l1Granted = true; + expect( l2Granted ).toBe( false ); + } ); + + const check2 = l2.then( () => { + l2Granted = true; + expect( l1Granted ).toBe( false ); + } ); + + // unlock both + const lock1 = await l1; + locks.release( lock1 ); + l1Granted = false; + + const lock2 = await l2; + locks.release( lock2 ); + l2Granted = false; + + // ensure that both locks were granted and checked + return await Promise.all( [ check1, check2 ] ); + } ); + + it( 'does not grant two exclusive locks to parent and child', async () => { + const locks = createLocks(); + + let l1Granted = false; + let l2Granted = false; + + // request two locks + const l1 = locks.acquire( 'store', [ 'root' ], true ); + const l2 = locks.acquire( 'store', [ 'root', 'child' ], true ); + + // on each grant, verify that the other lock is not granted at the same time + const check1 = l1.then( () => { + l1Granted = true; + expect( l2Granted ).toBe( false ); + } ); + + const check2 = l2.then( () => { + l2Granted = true; + expect( l1Granted ).toBe( false ); + } ); + + // unlock both + const lock1 = await l1; + locks.release( lock1 ); + l1Granted = false; + + const lock2 = await l2; + locks.release( lock2 ); + l2Granted = false; + + // ensure that both locks were granted and checked + return await Promise.all( [ check1, check2 ] ); + } ); + + it( 'grants two non-exclusive locks at once', async () => { + const locks = createLocks(); + + const l1 = await locks.acquire( 'store', [ 'root' ], false ); + const l2 = await locks.acquire( 'store', [ 'root' ], false ); + + expect( l1 ).not.toBeUndefined(); + expect( l2 ).not.toBeUndefined(); + } ); + + it( 'grants two exclusive locks to different branches', async () => { + const locks = createLocks(); + + const l1 = await locks.acquire( 'store', [ 'a' ], true ); + const l2 = await locks.acquire( 'store', [ 'b' ], true ); + + expect( l1 ).not.toBeUndefined(); + expect( l2 ).not.toBeUndefined(); + } ); +} ); + +/* eslint-enable jest/valid-expect-in-promise */ diff --git a/packages/core-data/src/locks/test/reducer.js b/packages/core-data/src/locks/test/reducer.js index b422ae9aa5ffa2..238531ebe137c6 100644 --- a/packages/core-data/src/locks/test/reducer.js +++ b/packages/core-data/src/locks/test/reducer.js @@ -6,7 +6,7 @@ import deepFreeze from 'deep-freeze'; /** * Internal dependencies */ -import { locks } from '../reducer'; +import locks from '../reducer'; const buildNode = ( children = {} ) => ( { locks: [], diff --git a/packages/core-data/src/locks/test/selectors.js b/packages/core-data/src/locks/test/selectors.js index 2f3f1cf77f707d..667d93c635c002 100644 --- a/packages/core-data/src/locks/test/selectors.js +++ b/packages/core-data/src/locks/test/selectors.js @@ -6,58 +6,45 @@ import deepFreeze from 'deep-freeze'; /** * Internal dependencies */ -import { - __unstableGetPendingLockRequests, - __unstableIsLockAvailable, -} from '../selectors'; +import { getPendingLockRequests, isLockAvailable } from '../selectors'; import { deepCopyLocksTreePath, getNode } from '../utils'; -describe( '__unstableGetPendingLockRequests', () => { +describe( 'getPendingLockRequests', () => { it( 'returns pending lock requests', () => { const state = deepFreeze( { - locks: { - requests: [ 1, 2, 3 ], - }, + requests: [ 1, 2, 3 ], } ); - expect( __unstableGetPendingLockRequests( state ) ).toEqual( [ - 1, - 2, - 3, - ] ); + expect( getPendingLockRequests( state ) ).toEqual( [ 1, 2, 3 ] ); } ); } ); -describe( '__unstableIsLockAvailable', () => { +describe( 'isLockAvailable', () => { describe( 'smoke tests', () => { it( 'returns true if lock is available', () => { const state = deepFreeze( { - locks: { - tree: { - children: {}, - locks: [], - }, + tree: { + children: {}, + locks: [], }, } ); expect( - __unstableIsLockAvailable( state, 'core', [], { + isLockAvailable( state, 'core', [], { exclusive: true, } ) ).toBe( true ); } ); it( 'returns false if lock is not available', () => { const state = deepFreeze( { - locks: { - tree: { - children: {}, - locks: [ { exclusive: false } ], - }, + tree: { + children: {}, + locks: [ { exclusive: false } ], }, } ); expect( - __unstableIsLockAvailable( state, 'core', [], { + isLockAvailable( state, 'core', [], { exclusive: true, } ) ).toBe( false ); @@ -75,7 +62,7 @@ describe( '__unstableIsLockAvailable', () => { } ); it( `returns true if no parent or descendant has any locks`, () => { expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -89,7 +76,7 @@ describe( '__unstableIsLockAvailable', () => { exclusive: true, } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -103,7 +90,7 @@ describe( '__unstableIsLockAvailable', () => { exclusive: true, } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -114,41 +101,39 @@ describe( '__unstableIsLockAvailable', () => { it( `returns true if another branch holds a locks (3)`, () => { const subState = { - locks: { - tree: { - locks: [], - children: { - postType: { - locks: [], - children: { - post: { - locks: [], - children: { - 16: { - locks: [ - { - store: 'core', - path: [ - 'entities', - 'data', - 'postType', - 'post', - 16, - ], - exclusive: true, - }, - ], - children: {}, - }, + tree: { + locks: [], + children: { + postType: { + locks: [], + children: { + post: { + locks: [], + children: { + 16: { + locks: [ + { + store: 'core', + path: [ + 'entities', + 'data', + 'postType', + 'post', + 16, + ], + exclusive: true, + }, + ], + children: {}, }, }, - wp_template_part: { - locks: [], - children: { - 17: { - locks: [], - children: {}, - }, + }, + wp_template_part: { + locks: [], + children: { + 17: { + locks: [], + children: {}, }, }, }, @@ -158,7 +143,7 @@ describe( '__unstableIsLockAvailable', () => { }, }; expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( subState ), 'core', [ 'postType', 'wp_template_part', 17 ], @@ -169,41 +154,39 @@ describe( '__unstableIsLockAvailable', () => { it( `returns true if another branch holds a locks (4)`, () => { const subState = { - locks: { - tree: { - locks: [], - children: { - core: { - locks: [], - children: { - entities: { - locks: [], - children: { - data: { - locks: [], - children: { - postType: { - locks: [], - children: { - book: { - locks: [], - children: { - 67: { - locks: [ - { - path: [ - 'core', - 'entities', - 'data', - 'postType', - 'book', - 67, - ], - exclusive: true, - }, - ], - children: {}, - }, + tree: { + locks: [], + children: { + core: { + locks: [], + children: { + entities: { + locks: [], + children: { + data: { + locks: [], + children: { + postType: { + locks: [], + children: { + book: { + locks: [], + children: { + 67: { + locks: [ + { + path: [ + 'core', + 'entities', + 'data', + 'postType', + 'book', + 67, + ], + exclusive: true, + }, + ], + children: {}, }, }, }, @@ -219,7 +202,7 @@ describe( '__unstableIsLockAvailable', () => { }, }; expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( subState ), 'core', [ 'entities', 'data', 'postType', 'book', 67 ], @@ -231,7 +214,7 @@ describe( '__unstableIsLockAvailable', () => { [ true, false ].forEach( ( exclusive ) => { it( `returns true if the path is not accessible and no parent holds a lock`, () => { expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'fake', 'path' ], @@ -245,7 +228,7 @@ describe( '__unstableIsLockAvailable', () => { exclusive, } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'fake', 'path' ], @@ -259,7 +242,7 @@ describe( '__unstableIsLockAvailable', () => { exclusive, } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -273,7 +256,7 @@ describe( '__unstableIsLockAvailable', () => { exclusive, } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -287,7 +270,7 @@ describe( '__unstableIsLockAvailable', () => { exclusive, } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -306,7 +289,7 @@ describe( '__unstableIsLockAvailable', () => { } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -325,7 +308,7 @@ describe( '__unstableIsLockAvailable', () => { } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -345,7 +328,7 @@ describe( '__unstableIsLockAvailable', () => { } ); it( `returns true if no parent or descendant has any locks`, () => { expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -361,7 +344,7 @@ describe( '__unstableIsLockAvailable', () => { exclusive: isOtherLockExclusive, } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'fake', 'path' ], @@ -375,7 +358,7 @@ describe( '__unstableIsLockAvailable', () => { exclusive: isOtherLockExclusive, } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -389,7 +372,7 @@ describe( '__unstableIsLockAvailable', () => { exclusive: isOtherLockExclusive, } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -403,7 +386,7 @@ describe( '__unstableIsLockAvailable', () => { exclusive: isOtherLockExclusive, } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -422,7 +405,7 @@ describe( '__unstableIsLockAvailable', () => { } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -441,7 +424,7 @@ describe( '__unstableIsLockAvailable', () => { } ); expect( - __unstableIsLockAvailable( + isLockAvailable( deepFreeze( state ), 'core', [ 'entities', 'root' ], @@ -454,20 +437,18 @@ describe( '__unstableIsLockAvailable', () => { } ); function appendLock( state, store, path, lock ) { - getNode( state.locks.tree, [ store, ...path ] ).locks.push( lock ); + getNode( state.tree, [ store, ...path ] ).locks.push( lock ); } function buildState( paths ) { return { - locks: { - requests: [], - tree: paths.reduce( - ( tree, path ) => deepCopyLocksTreePath( tree, path ), - { - locks: [], - children: {}, - } - ), - }, + requests: [], + tree: paths.reduce( + ( tree, path ) => deepCopyLocksTreePath( tree, path ), + { + locks: [], + children: {}, + } + ), }; } diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index 4b975e9a47efd4..f78e6fd76eff9d 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -15,7 +15,6 @@ import isShallowEqual from '@wordpress/is-shallow-equal'; import { ifMatchingAction, replaceAction } from './utils'; import { reducer as queriedDataReducer } from './queried-data'; import { defaultEntities, DEFAULT_ENTITY_KEY } from './entities'; -import { reducer as locksReducer } from './locks'; /** * Reducer managing terms state. Keyed by taxonomy slug, the value is either @@ -580,5 +579,4 @@ export default combineReducers( { embedPreviews, userPermissions, autosaves, - locks: locksReducer, } ); diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 0d459c0d0ba65b..7026937858b474 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -30,10 +30,6 @@ import { } from './actions'; import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; import { ifNotResolved, getNormalizedCommaSeparable } from './utils'; -import { - __unstableAcquireStoreLock, - __unstableReleaseStoreLock, -} from './locks'; /** * Requests authors from the REST API. @@ -74,11 +70,14 @@ export function* getEntityRecord( kind, name, key = '', query ) { return; } - const lock = yield* __unstableAcquireStoreLock( + const lock = yield controls.dispatch( + STORE_NAME, + '__unstableAcquireStoreLock', STORE_NAME, [ 'entities', 'data', kind, name, key ], { exclusive: false } ); + try { if ( query !== undefined && query._fields ) { // If requesting specific fields, items and query association to said @@ -129,7 +128,11 @@ export function* getEntityRecord( kind, name, key = '', query ) { // We need a way to handle and access REST API errors in state // Until then, catching the error ensures the resolver is marked as resolved. } finally { - yield* __unstableReleaseStoreLock( lock ); + yield controls.dispatch( + STORE_NAME, + '__unstableReleaseStoreLock', + lock + ); } } @@ -163,11 +166,14 @@ export function* getEntityRecords( kind, name, query = {} ) { return; } - const lock = yield* __unstableAcquireStoreLock( + const lock = yield controls.dispatch( + STORE_NAME, + '__unstableAcquireStoreLock', STORE_NAME, [ 'entities', 'data', kind, name ], { exclusive: false } ); + try { if ( query._fields ) { // If requesting specific fields, items and query association to said @@ -225,7 +231,11 @@ export function* getEntityRecords( kind, name, query = {} ) { }; } } finally { - yield* __unstableReleaseStoreLock( lock ); + yield controls.dispatch( + STORE_NAME, + '__unstableReleaseStoreLock', + lock + ); } } diff --git a/packages/core-data/src/test/actions.js b/packages/core-data/src/test/actions.js index 8ee805c489853c..302645687391d5 100644 --- a/packages/core-data/src/test/actions.js +++ b/packages/core-data/src/test/actions.js @@ -19,19 +19,6 @@ import { __experimentalBatch, } from '../actions'; -jest.mock( '../locks/actions', () => ( { - __unstableAcquireStoreLock: jest.fn( () => [ - { - type: 'MOCKED_ACQUIRE_LOCK', - }, - ] ), - __unstableReleaseStoreLock: jest.fn( () => [ - { - type: 'MOCKED_RELEASE_LOCK', - }, - ] ), -} ) ); - jest.mock( '../batch', () => { const { createBatch } = jest.requireActual( '../batch' ); return { @@ -74,7 +61,7 @@ describe( 'deleteEntityRecord', () => { // Acquire lock expect( fulfillment.next( entities ).value.type ).toBe( - 'MOCKED_ACQUIRE_LOCK' + '@@data/DISPATCH' ); // Start @@ -96,9 +83,7 @@ describe( 'deleteEntityRecord', () => { ); // Release lock - expect( fulfillment.next().value.type ).toEqual( - 'MOCKED_RELEASE_LOCK' - ); + expect( fulfillment.next().value.type ).toEqual( '@@data/DISPATCH' ); expect( fulfillment.next() ).toMatchObject( { done: true, @@ -124,6 +109,8 @@ describe( 'saveEntityRecord', () => { const dispatch = Object.assign( jest.fn(), { receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), } ); // Provide entities dispatch.mockReturnValueOnce( entities ); @@ -147,7 +134,7 @@ describe( 'saveEntityRecord', () => { data: post, } ); - expect( dispatch ).toHaveBeenCalledTimes( 5 ); + expect( dispatch ).toHaveBeenCalledTimes( 3 ); expect( dispatch ).toHaveBeenCalledWith( { type: 'SAVE_ENTITY_RECORD_START', kind: 'postType', @@ -155,11 +142,9 @@ describe( 'saveEntityRecord', () => { recordId: undefined, isAutosave: false, } ); - expect( dispatch ).toHaveBeenCalledWith( [ - { - type: 'MOCKED_ACQUIRE_LOCK', - }, - ] ); + expect( dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledTimes( + 1 + ); expect( dispatch ).toHaveBeenCalledWith( { type: 'SAVE_ENTITY_RECORD_FINISH', kind: 'postType', @@ -168,11 +153,9 @@ describe( 'saveEntityRecord', () => { error: undefined, isAutosave: false, } ); - expect( dispatch ).toHaveBeenCalledWith( [ - { - type: 'MOCKED_RELEASE_LOCK', - }, - ] ); + expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes( + 1 + ); expect( dispatch.receiveEntityRecords ).toHaveBeenCalledTimes( 1 ); expect( dispatch.receiveEntityRecords ).toHaveBeenCalledWith( @@ -198,6 +181,8 @@ describe( 'saveEntityRecord', () => { const dispatch = Object.assign( jest.fn(), { receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), } ); // Provide entities dispatch.mockReturnValueOnce( entities ); @@ -221,7 +206,7 @@ describe( 'saveEntityRecord', () => { data: post, } ); - expect( dispatch ).toHaveBeenCalledTimes( 5 ); + expect( dispatch ).toHaveBeenCalledTimes( 3 ); expect( dispatch ).toHaveBeenCalledWith( { type: 'SAVE_ENTITY_RECORD_START', kind: 'postType', @@ -229,11 +214,9 @@ describe( 'saveEntityRecord', () => { recordId: 10, isAutosave: false, } ); - expect( dispatch ).toHaveBeenCalledWith( [ - { - type: 'MOCKED_ACQUIRE_LOCK', - }, - ] ); + expect( dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledTimes( + 1 + ); expect( dispatch ).toHaveBeenCalledWith( { type: 'SAVE_ENTITY_RECORD_FINISH', kind: 'postType', @@ -242,11 +225,9 @@ describe( 'saveEntityRecord', () => { error: undefined, isAutosave: false, } ); - expect( dispatch ).toHaveBeenCalledWith( [ - { - type: 'MOCKED_RELEASE_LOCK', - }, - ] ); + expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes( + 1 + ); expect( dispatch.receiveEntityRecords ).toHaveBeenCalledTimes( 1 ); expect( dispatch.receiveEntityRecords ).toHaveBeenCalledWith( @@ -277,6 +258,8 @@ describe( 'saveEntityRecord', () => { const dispatch = Object.assign( jest.fn(), { receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), } ); // Provide entities dispatch.mockReturnValueOnce( entities ); @@ -297,7 +280,7 @@ describe( 'saveEntityRecord', () => { data: postType, } ); - expect( dispatch ).toHaveBeenCalledTimes( 5 ); + expect( dispatch ).toHaveBeenCalledTimes( 3 ); expect( dispatch ).toHaveBeenCalledWith( { type: 'SAVE_ENTITY_RECORD_START', kind: 'root', @@ -305,11 +288,9 @@ describe( 'saveEntityRecord', () => { recordId: 'page', isAutosave: false, } ); - expect( dispatch ).toHaveBeenCalledWith( [ - { - type: 'MOCKED_ACQUIRE_LOCK', - }, - ] ); + expect( dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledTimes( + 1 + ); expect( dispatch ).toHaveBeenCalledWith( { type: 'SAVE_ENTITY_RECORD_FINISH', kind: 'root', @@ -318,11 +299,9 @@ describe( 'saveEntityRecord', () => { error: undefined, isAutosave: false, } ); - expect( dispatch ).toHaveBeenCalledWith( [ - { - type: 'MOCKED_RELEASE_LOCK', - }, - ] ); + expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes( + 1 + ); expect( dispatch.receiveEntityRecords ).toHaveBeenCalledTimes( 1 ); expect( dispatch.receiveEntityRecords ).toHaveBeenCalledWith( diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index 0cb095dc5fb874..2ff24b126ab8d7 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -22,19 +22,6 @@ import { receiveCurrentUser, } from '../actions'; -jest.mock( '../locks/actions', () => ( { - __unstableAcquireStoreLock: jest.fn( () => [ - { - type: 'MOCKED_ACQUIRE_LOCK', - }, - ] ), - __unstableReleaseStoreLock: jest.fn( () => [ - { - type: 'MOCKED_RELEASE_LOCK', - }, - ] ), -} ) ); - describe( 'getEntityRecord', () => { const POST_TYPE = { slug: 'post' }; const ENTITIES = [ @@ -52,7 +39,7 @@ describe( 'getEntityRecord', () => { fulfillment.next(); // Provide entities and acquire lock expect( fulfillment.next( ENTITIES ).value.type ).toEqual( - 'MOCKED_ACQUIRE_LOCK' + '@@data/DISPATCH' ); // trigger apiFetch const { value: apiFetchAction } = fulfillment.next(); @@ -65,9 +52,7 @@ describe( 'getEntityRecord', () => { receiveEntityRecords( 'root', 'postType', POST_TYPE ) ); // Release lock - expect( fulfillment.next().value.type ).toEqual( - 'MOCKED_RELEASE_LOCK' - ); + expect( fulfillment.next().value.type ).toEqual( '@@data/DISPATCH' ); } ); it( 'accepts a query that overrides default api path', async () => { @@ -86,7 +71,7 @@ describe( 'getEntityRecord', () => { // Provide entities and acquire lock expect( fulfillment.next( ENTITIES ).value.type ).toEqual( - 'MOCKED_ACQUIRE_LOCK' + '@@data/DISPATCH' ); // Check resolution cache for an existing entity that fulfills the request with query @@ -108,9 +93,7 @@ describe( 'getEntityRecord', () => { ); // Release lock - expect( fulfillment.next().value.type ).toEqual( - 'MOCKED_RELEASE_LOCK' - ); + expect( fulfillment.next().value.type ).toEqual( '@@data/DISPATCH' ); } ); } ); @@ -168,7 +151,7 @@ describe( 'getEntityRecords', () => { fulfillment.next(); // Provide entities and acquire lock expect( fulfillment.next( ENTITIES ).value.type ).toEqual( - 'MOCKED_ACQUIRE_LOCK' + '@@data/DISPATCH' ); fulfillment.next(); fulfillment.next( POST_TYPES ); @@ -178,9 +161,7 @@ describe( 'getEntityRecords', () => { fulfillment.next(); // Release lock - expect( fulfillment.next().value.type ).toEqual( - 'MOCKED_RELEASE_LOCK' - ); + expect( fulfillment.next().value.type ).toEqual( '@@data/DISPATCH' ); } ); it( 'marks specific entity records as resolved', async () => { From 93ab227ef76a1a73720582b6958cf1a079a91aa8 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Wed, 1 Sep 2021 18:23:27 +0300 Subject: [PATCH 101/214] [Docs]: Update block variations documentation about `block` scope (#34455) --- .../block-api/block-variations.md | 2 +- .../block-variation-picker/README.md | 61 ++++++++++++++----- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/docs/reference-guides/block-api/block-variations.md b/docs/reference-guides/block-api/block-variations.md index 2c9e6b3bc6520b..4aa2122dcfd760 100644 --- a/docs/reference-guides/block-api/block-variations.md +++ b/docs/reference-guides/block-api/block-variations.md @@ -45,7 +45,7 @@ An object describing a variation defined for the block type can contain the foll - `example` (optional, type `Object`) – Example provides structured data for the block preview. You can set to `undefined` to disable the preview shown for the block type. - `scope` (optional, type `WPBlockVariationScope[]`) - the list of scopes where the variation is applicable. When not provided, it defaults to `block` and `inserter`. Available options: - `inserter` - Block Variation is shown on the inserter. - - `block` - Used by blocks to filter specific block variations. Mostly used in Placeholder patterns like `Columns` block. + - `block` - Used by blocks to filter specific block variations. `Columns` and `Query Loop` blocks have such variations and are passed to the [experimental BlockVariationPicker](/packages/block-editor/src/components/block-variation-picker/README.md) component, which is handling the displaying of variations and the ability to select one from them. - `transform` - Block Variation will be shown in the component for Block Variations transformations. - `keywords` (optional, type `string[]`) - An array of terms (which can be translated) that help users discover the variation while searching. - `isActive` (optional, type `Function|string[]`) - This can be a function or an array of block attributes. Function that accepts a block's attributes and the variation's attributes and determines if a variation is active. This function doesn't try to find a match dynamically based on all block's attributes, as in many cases some attributes are irrelevant. An example would be for `embed` block where we only care about `providerNameSlug` attribute's value. We can also use a `string[]` to tell which attributes should be compared as a shorthand. Each attributes will be matched and the variation will be active if all of them are matching. diff --git a/packages/block-editor/src/components/block-variation-picker/README.md b/packages/block-editor/src/components/block-variation-picker/README.md index 117e79a6ee648d..1de44a9bcd406f 100644 --- a/packages/block-editor/src/components/block-variation-picker/README.md +++ b/packages/block-editor/src/components/block-variation-picker/README.md @@ -1,8 +1,12 @@ # Block Variation Picker -The `BlockVariationPicker` component allows to display for certain types of blocks their different variations, and to choose one of them. +
+This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. +
-This component is currently used by the "Columns" block to display and choose the number and structure of columns. It is also used by the "Post Hierarchical Terms Block" block. +The `BlockVariationPicker` component allows certain types of blocks to display their different variations, and to choose one of them. Variations provided are usually filtered by their inclusion of the `block` value in their `scope` attribute. + +This component is currently used by "Columns" and "Query Loop" blocks. ![Columns block variations](https://make.wordpress.org/core/files/2020/09/colums-block-variations.png) @@ -18,33 +22,62 @@ This component is currently used by the "Columns" block to display and choose th Renders the variations of a block. ```jsx -import { BlockVariationPicker } from '@wordpress/block-editor'; - -const MyBlockVariationPicker = () => ( - -); +import { useSelect } from '@wordpress/data'; +import { + __experimentalBlockVariationPicker as BlockVariationPicker, + store as blockEditorStore, +} from '@wordpress/block-editor'; + +const MyBlockVariationPicker = ( { blockName } ) => { + const variations = useSelect( + ( select ) => { + const { getBlockVariations } = select( blocksStore ); + return getBlockVariations( blockName, 'block' ); + }, + [ blockName ] + ); + return ; +}; ``` ### Props -#### label - -The label of each variation of the block. +#### `label` - Type: `String` +- Required: No +- Default: `Choose variation` -#### instructions +The label of each variation of the block. -The instructions to choose a block variation. +#### `instructions` - Type: `String` +- Required: No +- Default: `Select a variation to start with.` + +The instructions to choose a block variation. -#### variations +#### `variations` -- Type: `Array` +- Type: `Array` The different variations of the block. +#### `onSelect` + +- Type: `Function` + +Callback called when a block variation is selected. It recieves the selected variation as a parameter. + +#### `icon` + +- Type: `Icon component` +- Required: No +- Default: `layout` + +Icon to be displayed at the top of the component before the `label`. + ## Related components Block Editor components are components that can be used to compose the UI of your block editor. Thus, they can only be used under a [BlockEditorProvider](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/provider/README.md) in the components tree. From 412b01589c68b1e110a9a2d643272a7b23d8fec9 Mon Sep 17 00:00:00 2001 From: Mike Schroder <1034160+getsource@users.noreply.github.com> Date: Thu, 2 Sep 2021 00:28:46 +0900 Subject: [PATCH 102/214] Video Block: Fix TypeError when removing poster. (#34411) --- packages/block-library/src/video/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index 658640158bd4dc..fc2887676815e9 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -154,7 +154,7 @@ function VideoEdit( { setAttributes( { poster: '' } ); // Move focus back to the Media Upload button. - this.posterImageButton.current.focus(); + posterImageButton.current.focus(); } const videoPosterDescription = `video-block__poster-image-description-${ instanceId }`; From 06f2447845608efccd6046bbb29bcc60ea51f1fd Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Wed, 1 Sep 2021 17:49:06 +0200 Subject: [PATCH 103/214] Refactor deleteEntityRecord to use thunks instead of generators (#34386) * Refactor deleteEntityRecord to use thunks instead of generators * Remove await from two synchronous dispatch calls * Adjust tests --- docs/reference-guides/data/data-core.md | 2 +- packages/core-data/README.md | 2 +- packages/core-data/src/actions.js | 45 ++++++---------- packages/core-data/src/test/actions.js | 68 +++++++++++++++---------- 4 files changed, 60 insertions(+), 57 deletions(-) diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index cfecc33470ab6c..ce93ad2aedea70 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -535,7 +535,7 @@ _Parameters_ - _recordId_ `string`: Record ID of the deleted entity. - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. -- _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a control descriptor. +- _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. ### editEntityRecord diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 4e906e79bb1be3..82754d2cbb711a 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -69,7 +69,7 @@ _Parameters_ - _recordId_ `string`: Record ID of the deleted entity. - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. -- _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a control descriptor. +- _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. ### editEntityRecord diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 49c624977e48dc..d06bc6d9d391cd 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -8,7 +8,7 @@ import { v4 as uuid } from 'uuid'; * WordPress dependencies */ import { controls } from '@wordpress/data'; -import { apiFetch, __unstableAwaitPromise } from '@wordpress/data-controls'; +import { __unstableAwaitPromise } from '@wordpress/data-controls'; import triggerFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; @@ -162,16 +162,16 @@ export function receiveEmbedPreview( url, preview ) { * @param {Object} [options] Delete options. * @param {Function} [options.__unstableFetch] Internal use only. Function to * call instead of `apiFetch()`. - * Must return a control descriptor. + * Must return a promise. */ -export function* deleteEntityRecord( +export const deleteEntityRecord = ( kind, name, recordId, query, - { __unstableFetch = null } = {} -) { - const entities = yield getKindEntities( kind ); + { __unstableFetch = triggerFetch } = {} +) => async ( { dispatch } ) => { + const entities = await dispatch( getKindEntities( kind ) ); const entity = find( entities, { kind, name } ); let error; let deletedRecord = false; @@ -179,21 +179,19 @@ export function* deleteEntityRecord( return; } - const lock = yield controls.dispatch( - STORE_NAME, - '__unstableAcquireStoreLock', + const lock = await dispatch.__unstableAcquireStoreLock( STORE_NAME, [ 'entities', 'data', kind, name, recordId ], { exclusive: true } ); try { - yield { + dispatch( { type: 'DELETE_ENTITY_RECORD_START', kind, name, recordId, - }; + } ); try { let path = `${ entity.baseURL }/${ recordId }`; @@ -202,40 +200,29 @@ export function* deleteEntityRecord( path = addQueryArgs( path, query ); } - const options = { + deletedRecord = await __unstableFetch( { path, method: 'DELETE', - }; - if ( __unstableFetch ) { - deletedRecord = yield __unstableAwaitPromise( - __unstableFetch( options ) - ); - } else { - deletedRecord = yield apiFetch( options ); - } + } ); - yield removeItems( kind, name, recordId, true ); + await dispatch( removeItems( kind, name, recordId, true ) ); } catch ( _error ) { error = _error; } - yield { + dispatch( { type: 'DELETE_ENTITY_RECORD_FINISH', kind, name, recordId, error, - }; + } ); return deletedRecord; } finally { - yield controls.dispatch( - STORE_NAME, - '__unstableReleaseStoreLock', - lock - ); + dispatch.__unstableReleaseStoreLock( lock ); } -} +}; /** * Returns an action object that triggers an diff --git a/packages/core-data/src/test/actions.js b/packages/core-data/src/test/actions.js index 302645687391d5..9b242cadcf862e 100644 --- a/packages/core-data/src/test/actions.js +++ b/packages/core-data/src/test/actions.js @@ -49,46 +49,62 @@ describe( 'editEntityRecord', () => { } ); describe( 'deleteEntityRecord', () => { + beforeEach( async () => { + apiFetch.mockReset(); + jest.useFakeTimers(); + } ); + it( 'triggers a DELETE request for an existing record', async () => { - const post = 10; + const deletedRecord = { title: 'new post', id: 10 }; const entities = [ { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' }, ]; - const fulfillment = deleteEntityRecord( 'postType', 'post', post ); - // Trigger generator - fulfillment.next(); + const dispatch = Object.assign( jest.fn(), { + receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), + } ); + // Provide entities + dispatch.mockReturnValueOnce( entities ); - // Acquire lock - expect( fulfillment.next( entities ).value.type ).toBe( - '@@data/DISPATCH' - ); + // Provide response + apiFetch.mockImplementation( () => deletedRecord ); - // Start - expect( fulfillment.next().value.type ).toEqual( - 'DELETE_ENTITY_RECORD_START' - ); + const result = await deleteEntityRecord( + 'postType', + 'post', + deletedRecord.id + )( { dispatch } ); - // delete api call - const { value: apiFetchAction } = fulfillment.next(); - expect( apiFetchAction.request ).toEqual( { + expect( apiFetch ).toHaveBeenCalledTimes( 1 ); + expect( apiFetch ).toHaveBeenCalledWith( { path: '/wp/v2/posts/10', method: 'DELETE', } ); - expect( fulfillment.next().value.type ).toBe( 'REMOVE_ITEMS' ); - - expect( fulfillment.next().value.type ).toBe( - 'DELETE_ENTITY_RECORD_FINISH' + expect( dispatch ).toHaveBeenCalledTimes( 4 ); + expect( dispatch ).toHaveBeenCalledWith( { + type: 'DELETE_ENTITY_RECORD_START', + kind: 'postType', + name: 'post', + recordId: 10, + } ); + expect( dispatch ).toHaveBeenCalledWith( { + type: 'DELETE_ENTITY_RECORD_FINISH', + kind: 'postType', + name: 'post', + recordId: 10, + error: undefined, + } ); + expect( dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledTimes( + 1 + ); + expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes( + 1 ); - // Release lock - expect( fulfillment.next().value.type ).toEqual( '@@data/DISPATCH' ); - - expect( fulfillment.next() ).toMatchObject( { - done: true, - value: undefined, - } ); + expect( result ).toBe( deletedRecord ); } ); } ); From 8d8262c87c82acfc676bba076085b5ba28f0b0fd Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Wed, 1 Sep 2021 17:49:18 +0200 Subject: [PATCH 104/214] Refactor editEntityRecord, undo, and redo to be thunks instead of generators (#34387) * Refactor editEntityRecord, undo, and redo to be async instead of generator-based * Don't return anything from undo() and redo() * Restore missing import --- docs/reference-guides/data/data-core.md | 8 ++++ packages/core-data/README.md | 8 ++++ packages/core-data/src/actions.js | 64 +++++++++++-------------- packages/core-data/src/test/actions.js | 16 +++---- 4 files changed, 52 insertions(+), 44 deletions(-) diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index ce93ad2aedea70..16f4d6305fecf1 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -680,6 +680,10 @@ _Returns_ Action triggered to redo the last undoed edit to an entity record, if any. +_Returns_ + +- `undefined`: + ### saveEditedEntityRecord Action triggered to save an entity record's edits. @@ -709,4 +713,8 @@ _Parameters_ Action triggered to undo the last edit to an entity record, if any. +_Returns_ + +- `undefined`: + diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 82754d2cbb711a..5d9ee318a0af8c 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -214,6 +214,10 @@ _Returns_ Action triggered to redo the last undoed edit to an entity record, if any. +_Returns_ + +- `undefined`: + ### saveEditedEntityRecord Action triggered to save an entity record's edits. @@ -243,6 +247,10 @@ _Parameters_ Action triggered to undo the last edit to an entity record, if any. +_Returns_ + +- `undefined`: + ## Selectors diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index d06bc6d9d391cd..2fc8e51a279242 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -237,28 +237,22 @@ export const deleteEntityRecord = ( * * @return {Object} Action object. */ -export function* editEntityRecord( kind, name, recordId, edits, options = {} ) { - const entity = yield controls.select( STORE_NAME, 'getEntity', kind, name ); +export const editEntityRecord = ( + kind, + name, + recordId, + edits, + options = {} +) => async ( { select, dispatch } ) => { + const entity = select.getEntity( kind, name ); if ( ! entity ) { throw new Error( `The entity being edited (${ kind }, ${ name }) does not have a loaded config.` ); } const { transientEdits = {}, mergedEdits = {} } = entity; - const record = yield controls.select( - STORE_NAME, - 'getRawEntityRecord', - kind, - name, - recordId - ); - const editedRecord = yield controls.select( - STORE_NAME, - 'getEditedEntityRecord', - kind, - name, - recordId - ); + const record = select.getRawEntityRecord( kind, name, recordId ); + const editedRecord = select.getEditedEntityRecord( kind, name, recordId ); const edit = { kind, @@ -277,7 +271,7 @@ export function* editEntityRecord( kind, name, recordId, edits, options = {} ) { }, {} ), transientEdits, }; - return { + return await dispatch( { type: 'EDIT_ENTITY_RECORD', ...edit, meta: { @@ -290,44 +284,44 @@ export function* editEntityRecord( kind, name, recordId, edits, options = {} ) { }, {} ), }, }, - }; -} + } ); +}; /** * Action triggered to undo the last edit to * an entity record, if any. + * + * @return {undefined} */ -export function* undo() { - const undoEdit = yield controls.select( STORE_NAME, 'getUndoEdit' ); +export const undo = () => ( { select, dispatch } ) => { + const undoEdit = select.getUndoEdit(); if ( ! undoEdit ) { return; } - yield { + dispatch( { type: 'EDIT_ENTITY_RECORD', ...undoEdit, - meta: { - isUndo: true, - }, - }; -} + meta: { isUndo: true }, + } ); +}; /** * Action triggered to redo the last undoed * edit to an entity record, if any. + * + * @return {undefined} */ -export function* redo() { - const redoEdit = yield controls.select( STORE_NAME, 'getRedoEdit' ); +export const redo = () => ( { select, dispatch } ) => { + const redoEdit = select.getRedoEdit(); if ( ! redoEdit ) { return; } - yield { + dispatch( { type: 'EDIT_ENTITY_RECORD', ...redoEdit, - meta: { - isRedo: true, - }, - }; -} + meta: { isRedo: true }, + } ); +}; /** * Forces the creation of a new undo level. diff --git a/packages/core-data/src/test/actions.js b/packages/core-data/src/test/actions.js index 9b242cadcf862e..7e015e44442e34 100644 --- a/packages/core-data/src/test/actions.js +++ b/packages/core-data/src/test/actions.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { controls } from '@wordpress/data'; import apiFetch from '@wordpress/api-fetch'; jest.mock( '@wordpress/api-fetch' ); @@ -29,20 +28,19 @@ jest.mock( '../batch', () => { } ); describe( 'editEntityRecord', () => { - it( 'throws when the edited entity does not have a loaded config.', () => { + it( 'throws when the edited entity does not have a loaded config.', async () => { const entity = { kind: 'someKind', name: 'someName', id: 'someId' }; + const select = { + getEntity: jest.fn(), + }; const fulfillment = editEntityRecord( entity.kind, entity.name, entity.id, {} - ); - expect( fulfillment.next().value ).toEqual( - controls.select( 'core', 'getEntity', entity.kind, entity.name ) - ); - - // Don't pass back an entity config. - expect( fulfillment.next.bind( fulfillment ) ).toThrow( + )( { select } ); + expect( select.getEntity ).toHaveBeenCalledTimes( 1 ); + await expect( fulfillment ).rejects.toThrow( `The entity being edited (${ entity.kind }, ${ entity.name }) does not have a loaded config.` ); } ); From 6e232d9a5f40d5036602c16bdc379d2901f7cb7f Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Wed, 1 Sep 2021 18:28:52 +0200 Subject: [PATCH 105/214] [RNMobile][Embed block] Alignment options (#33851) * Created an EmbedPreview component to house the caption. * Disabled the placeholder mode temporarily until the linking is enabled. * added the react-native-web-view package. * linked react-native-web-view to ios * integrated react-native-web-view with android * initial integration of WebView into the embed-preview component. * added project configurations for react-native-webview android * Add sandbox component native version * Use webview to preview embed block * Update aspect ratio calculation in sandbox component * Add native styles to sandbox component * Open embed content in external browser * Add comment to sandbox webview * Update react-native-webview package to wp-fork * Bump react-native-webview version * Bump react-native-webview version * Use provider url as base url for embed webview * Add forceRender param to trySandbox * Handle case of setting empty URL after preview * Enable embed preview interaction when selected * Add max allowed requests to embed preview * Prevent navigation in embed preview on Android * Recreate WebView in Android on orientation change * Disable embed preview interaction * Remove unused clientId prop from embed web version * Enable inline preview only in dev mode * Remove URL from embed block internal state I noticed that for the mobile version we don't really need to keep the URL in the internal state, as we were only modifying it once it changed, and that's already being handled with the url block's attribute. * Use memo in embed preview component * Use memo in sandbox component * Rename iframe html to content html and update logic * Bump react-native-webview package * Add align styles to embed preview * Add embed aspect ratio styles to sandbox static html * Remove width calculation from sandbox We really only need to calculate the height as the width should fit the block's size. * Add webview content style in sandbox * Disable scroll and zoom features in sandbox * Use html for rendering embed images Previously embed images were rendering the thumbnail instead of the original content. * Remove unused embed style fallbacks * Revert "Use html for rendering embed images" This reverts commit 34f16363802ae3177747c621f0f99472240cfb10. * Add comment to sandbox styles It references the styles from the web version that have been adapted for native version. Co-authored-by: Joel Dean --- .../block-library/src/embed/edit.native.js | 3 +- .../src/embed/embed-preview.native.js | 14 +++- .../src/embed/styles.native.scss | 18 +++++ .../components/src/sandbox/index.native.js | 81 ++++++++++++++----- .../components/src/sandbox/style.native.scss | 4 + 5 files changed, 100 insertions(+), 20 deletions(-) diff --git a/packages/block-library/src/embed/edit.native.js b/packages/block-library/src/embed/edit.native.js index 623f08752e56da..c0a0946a1fa86d 100644 --- a/packages/block-library/src/embed/edit.native.js +++ b/packages/block-library/src/embed/edit.native.js @@ -34,7 +34,7 @@ import { View } from '@wordpress/primitives'; const EmbedEdit = ( props ) => { const { - attributes: { providerNameSlug, previewable, responsive, url }, + attributes: { align, providerNameSlug, previewable, responsive, url }, attributes, isSelected, onReplace, @@ -234,6 +234,7 @@ const EmbedEdit = ( props ) => { /> { const [ isCaptionSelected, setIsCaptionSelected ] = useState( false ); + const wrapperStyle = styles[ 'embed-preview__wrapper' ]; + const wrapperAlignStyle = + styles[ `embed-preview__wrapper--align-${ align }` ]; + const sandboxAlignStyle = + styles[ `embed-preview__sandbox--align-${ align }` ]; + function accessibilityLabelCreator( caption ) { return isEmpty( caption ) ? /* translators: accessibility text. Empty Embed caption. */ @@ -91,13 +99,17 @@ const EmbedPreview = ( { } } } > - + diff --git a/packages/block-library/src/embed/styles.native.scss b/packages/block-library/src/embed/styles.native.scss index d1fb160a2341a4..fade4204a975bf 100644 --- a/packages/block-library/src/embed/styles.native.scss +++ b/packages/block-library/src/embed/styles.native.scss @@ -72,6 +72,24 @@ align-items: center; } +.embed-preview__wrapper { + flex: 1; +} + +.embed-preview__wrapper--align-left { + align-items: flex-start; +} +.embed-preview__wrapper--align-right { + align-items: flex-end; +} + +.embed-preview__sandbox--align-left { + max-width: 360px; +} +.embed-preview__sandbox--align-right { + max-width: 360px; +} + .embed-preview__loading--dark { background-color: $background-dark-secondary; } diff --git a/packages/components/src/sandbox/index.native.js b/packages/components/src/sandbox/index.native.js index 3c03e79a84ae7c..ef08c6f2729d14 100644 --- a/packages/components/src/sandbox/index.native.js +++ b/packages/components/src/sandbox/index.native.js @@ -92,22 +92,67 @@ const style = ` body > div iframe { width: 100%; } - html.wp-has-aspect-ratio, - body.wp-has-aspect-ratio, - body.wp-has-aspect-ratio > div, - body.wp-has-aspect-ratio > div iframe { - height: auto; - overflow: hidden; /* If it has an aspect ratio, it shouldn't scroll. */ - } body > div > * { margin-top: 0 !important; /* Has to have !important to override inline styles. */ margin-bottom: 0 !important; } + + .wp-block-embed__wrapper { + position: relative; + } + + body.wp-has-aspect-ratio > div iframe { + height: 100%; + overflow: hidden; /* If it has an aspect ratio, it shouldn't scroll. */ + } + + /** + * Add responsiveness to embeds with aspect ratios. + * + * These styles have been copied from the web version (https://git.io/JEFcX) and + * adapted for the native version. + */ + .wp-has-aspect-ratio.wp-block-embed__wrapper::before { + content: ""; + display: block; + padding-top: 50%; // Default to 2:1 aspect ratio. + } + .wp-has-aspect-ratio iframe { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + height: 100%; + width: 100%; + } + .wp-embed-aspect-21-9.wp-block-embed__wrapper::before { + padding-top: 42.85%; // 9 / 21 * 100 + } + .wp-embed-aspect-18-9.wp-block-embed__wrapper::before { + padding-top: 50%; // 9 / 18 * 100 + } + .wp-embed-aspect-16-9.wp-block-embed__wrapper::before { + padding-top: 56.25%; // 9 / 16 * 100 + } + .wp-embed-aspect-4-3.wp-block-embed__wrapper::before { + padding-top: 75%; // 3 / 4 * 100 + } + .wp-embed-aspect-1-1.wp-block-embed__wrapper::before { + padding-top: 100%; // 1 / 1 * 100 + } + .wp-embed-aspect-9-16.wp-block-embed__wrapper::before { + padding-top: 177.77%; // 16 / 9 * 100 + } + .wp-embed-aspect-1-2.wp-block-embed__wrapper::before { + padding-top: 200%; // 2 / 1 * 100 + } `; const EMPTY_ARRAY = []; function Sandbox( { + containerStyle, html = '', providerUrl = '', scripts = EMPTY_ARRAY, @@ -117,7 +162,6 @@ function Sandbox( { url, } ) { const ref = useRef(); - const [ width, setWidth ] = useState( 0 ); const [ height, setHeight ] = useState( 0 ); const [ contentHtml, setContentHtml ] = useState( getHtmlDoc() ); @@ -142,7 +186,7 @@ function Sandbox( { // Scripts go into the body rather than the head, to support embedded content such as Instagram // that expect the scripts to be part of the body. const htmlDoc = ( - + { title } ); } diff --git a/packages/components/src/sandbox/style.native.scss b/packages/components/src/sandbox/style.native.scss index cba25ab95f8aef..d591c6c84a2372 100644 --- a/packages/components/src/sandbox/style.native.scss +++ b/packages/components/src/sandbox/style.native.scss @@ -1,3 +1,7 @@ .sandbox-webview__container { + width: 100%; +} + +.sandbox-webview__content { background-color: transparent; } From 4623cf1e90db7373b63acd750380ee6ea0e60bac Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Wed, 1 Sep 2021 21:26:00 +0300 Subject: [PATCH 106/214] Fix linting error in trunk (#34464) --- packages/core-data/src/actions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 2fc8e51a279242..104cf63348a081 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -7,7 +7,6 @@ import { v4 as uuid } from 'uuid'; /** * WordPress dependencies */ -import { controls } from '@wordpress/data'; import { __unstableAwaitPromise } from '@wordpress/data-controls'; import triggerFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; From 82c6090aadf8f7b618f7b44ed65b1da5f01c60ae Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Wed, 1 Sep 2021 19:04:46 -0400 Subject: [PATCH 107/214] Upgrade gradle to 7.1.1 agp to 4.2.2 (#34048) * Upgrade Android projects to Gradle 7.1.1 * Upgrade Android projects to AGP 4.2.2 * Remove unnecessary maven plugin from react-native-aztec * Temporarily use Jitpack for all node_modules project dependencies * Prefer android:color/white over color/white in react-native-bridge * Update react-native-bridge project dependencies from implementation to api * Update react-native-svg & react-native-slider * Use plugin DSL in react-native-bridge * Re-add react-native-slider and react-native-svg to react-native-bridge android * Update react-native-linear-gradient & react-native-video * Updates remaining react-native libraries to Gradle 7 * Revert react-native-bridge project dependency changes * Re-add accidentally removed react-native-webview and update its version * Update react-native-aztec to use Plugin DSL * Adds agp & kotlin plugins (without applying) to root react-native-bridge build.gradle * Remove explicit kotlin dependency from react-native-bridge * Update react-native libraries: masked-view, reanimated, safe-area-context, screens * Update react-native-hsv-color-picker * Updating package.json Updating package.json for libraries to use new wp-fork for Gradle 7 * Updating package.json Including react-native-prompt-android wp-fork change. * Update package.json * Bumping forked dependency versions for Gradle 7 * Update package-lock file By running: `npm uninstall @wordpress/react-native-editor --save && npm install ./packages/react-native-editor --save` * Update react-native-gesture-handler fork Bring in the latest changes from react-native-gesture-handler's wp-fork branch * Temp force new NPM cache * Bump react-native-gesture-handler * Bump react-native-gesture-handler again * Bump react-native-gesture-handler * Temp force new NPM cache * Change preactjs/compressed-size-action to npm i * Change preactjs/compressed-size-action to npm install * Revert "Change preactjs/compressed-size-action to npm install" This reverts commit 6073a6b24aa0c3266e2416f0a9af3907fb4dcc79. * Revert "Change preactjs/compressed-size-action to npm i" This reverts commit bba53beaa14c8ec179ce7a62022bd040e1b89e20. * Revert "Temp force new NPM cache" This reverts commit 0260fa4bee59fde7e5b0de8ec345798cdbfbbfa7. * Revert "Temp force new NPM cache" This reverts commit 1d9bdb3b12c413320373c92342f08fa41f2c55f5. * Update react-native-gesture-handler version * Force CI cache to update (must be reverted before merge) * Revert "Force CI cache to update (must be reverted before merge)" This reverts commit ecb1829de5990058796bd91ace167b6a53ec87fa. Co-authored-by: illusaen Co-authored-by: Matt Chowning Co-authored-by: Paul Von Schrottky Co-authored-by: Ceyhun Ozugur --- package-lock.json | 80 +++++++++--------- .../react-native-aztec/android/build.gradle | 32 ++----- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58695 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- packages/react-native-aztec/android/gradlew | 33 +++----- .../android/settings.gradle | 22 ++++- .../react-native-bridge/android/build.gradle | 11 +-- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58695 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- packages/react-native-bridge/android/gradlew | 33 +++----- .../android/react-native-bridge/build.gradle | 38 ++------- .../src/main/res/drawable/ic_check_24px.xml | 2 +- .../src/main/res/drawable/ic_close_24px.xml | 2 +- .../layout/activity_gutenberg_web_view.xml | 6 +- .../android/settings.gradle | 22 ++++- .../react-native-editor/android/build.gradle | 2 +- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58695 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../react-native-editor/android/gradlew.bat | 3 - packages/react-native-editor/package.json | 26 +++--- 20 files changed, 150 insertions(+), 168 deletions(-) diff --git a/package-lock.json b/package-lock.json index d4899cfca2fa11..def5e7fc6713dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7824,12 +7824,12 @@ } }, "@react-native-community/masked-view": { - "version": "git+https://github.com/wordpress-mobile/react-native-masked-view.git#d849a1c7ed318196394aa20c3ae329431435e01f", - "from": "git+https://github.com/wordpress-mobile/react-native-masked-view.git#v0.1.11-wp" + "version": "git+https://github.com/wordpress-mobile/react-native-masked-view.git#ed1c3812e4982075279a3dca952afaa243ea1b4c", + "from": "git+https://github.com/wordpress-mobile/react-native-masked-view.git#v0.1.11-wp-1" }, "@react-native-community/slider": { - "version": "git+https://github.com/wordpress-mobile/react-native-slider.git#af01fce403b2c559c704fc87654f363ed2aea9fd", - "from": "git+https://github.com/wordpress-mobile/react-native-slider.git#v3.0.2-wp" + "version": "git+https://github.com/wordpress-mobile/react-native-slider.git#159fe48cb616cfbe684e06c2fbea5561f5c9cbfd", + "from": "git+https://github.com/wordpress-mobile/react-native-slider.git#v3.0.2-wp-1" }, "@react-native/assets": { "version": "1.0.0", @@ -18899,8 +18899,8 @@ "requires": { "@babel/runtime": "^7.13.10", "@react-native-community/blur": "3.6.0", - "@react-native-community/masked-view": "git+https://github.com/wordpress-mobile/react-native-masked-view.git#v0.1.11-wp", - "@react-native-community/slider": "git+https://github.com/wordpress-mobile/react-native-slider.git#v3.0.2-wp", + "@react-native-community/masked-view": "git+https://github.com/wordpress-mobile/react-native-masked-view.git#v0.1.11-wp-1", + "@react-native-community/slider": "git+https://github.com/wordpress-mobile/react-native-slider.git#v3.0.2-wp-1", "@react-navigation/core": "5.12.0", "@react-navigation/native": "5.7.0", "@react-navigation/routers": "5.4.9", @@ -18923,23 +18923,23 @@ "jsdom-jscore-rn": "git+https://github.com/iamcco/jsdom-jscore-rn.git#a562f3d57c27c13e5bfc8cf82d496e69a3ba2800", "node-fetch": "^2.6.0", "react-native": "0.64.0", - "react-native-gesture-handler": "git+https://github.com/wordpress-mobile/react-native-gesture-handler.git#1.10.1-wp-2", - "react-native-get-random-values": "git+https://github.com/wordpress-mobile/react-native-get-random-values.git#v1.4.0-wp", + "react-native-gesture-handler": "git+https://github.com/wordpress-mobile/react-native-gesture-handler.git#1.10.1-wp-3", + "react-native-get-random-values": "git+https://github.com/wordpress-mobile/react-native-get-random-values.git#v1.4.0-wp-1", "react-native-hr": "git+https://github.com/Riglerr/react-native-hr.git#2d01a5cf77212d100e8b99e0310cce5234f977b3", - "react-native-hsv-color-picker": "git+https://github.com/wordpress-mobile/react-native-hsv-color-picker.git#v1.0.1-wp", + "react-native-hsv-color-picker": "git+https://github.com/wordpress-mobile/react-native-hsv-color-picker.git#v1.0.1-wp-1", "react-native-keyboard-aware-scroll-view": "git+https://github.com/wordpress-mobile/react-native-keyboard-aware-scroll-view.git#v0.8.8-wp", - "react-native-linear-gradient": "git+https://github.com/wordpress-mobile/react-native-linear-gradient.git#v2.5.6-wp", + "react-native-linear-gradient": "git+https://github.com/wordpress-mobile/react-native-linear-gradient.git#v2.5.6-wp-1", "react-native-modal": "^11.10.0", - "react-native-prompt-android": "git+https://github.com/wordpress-mobile/react-native-prompt-android.git#v1.0.0-wp", - "react-native-reanimated": "git+https://github.com/wordpress-mobile/react-native-reanimated.git#1.9.0-wp", + "react-native-prompt-android": "git+https://github.com/wordpress-mobile/react-native-prompt-android.git#v1.0.0-wp-1", + "react-native-reanimated": "git+https://github.com/wordpress-mobile/react-native-reanimated.git#1.9.0-wp-1", "react-native-safe-area": "^0.5.0", - "react-native-safe-area-context": "git+https://github.com/wordpress-mobile/react-native-safe-area-context.git#v3.2.0-wp", + "react-native-safe-area-context": "git+https://github.com/wordpress-mobile/react-native-safe-area-context.git#v3.2.0-wp-1", "react-native-sass-transformer": "^1.1.1", - "react-native-screens": "git+https://github.com/wordpress-mobile/react-native-screens.git#2.9.0-wp", - "react-native-svg": "git+https://github.com/wordpress-mobile/react-native-svg.git#v9.13.7-wp", + "react-native-screens": "git+https://github.com/wordpress-mobile/react-native-screens.git#2.9.0-wp-1", + "react-native-svg": "git+https://github.com/wordpress-mobile/react-native-svg.git#v9.13.7-wp-1", "react-native-url-polyfill": "^1.1.2", - "react-native-video": "git+https://github.com/wordpress-mobile/react-native-video.git#5.0.2-wp", - "react-native-webview": "git+https://github.com/wordpress-mobile/react-native-webview.git#v11.6.5-wp" + "react-native-video": "git+https://github.com/wordpress-mobile/react-native-video.git#5.0.2-wp-1", + "react-native-webview": "git+https://github.com/wordpress-mobile/react-native-webview.git#v11.6.5-wp-1" } }, "@wordpress/readable-js-assets-webpack-plugin": { @@ -53388,8 +53388,8 @@ } }, "react-native-gesture-handler": { - "version": "git+https://github.com/wordpress-mobile/react-native-gesture-handler.git#0b284c3d26c2af4b99f9b631b9888df7e11502d0", - "from": "git+https://github.com/wordpress-mobile/react-native-gesture-handler.git#1.10.1-wp-2", + "version": "git+https://github.com/wordpress-mobile/react-native-gesture-handler.git#f1ae186d300b8de79b03c143001552165bf04756", + "from": "git+https://github.com/wordpress-mobile/react-native-gesture-handler.git#1.10.1-wp-3", "requires": { "@egjs/hammerjs": "^2.0.17", "fbjs": "^3.0.0", @@ -53399,8 +53399,8 @@ } }, "react-native-get-random-values": { - "version": "git+https://github.com/wordpress-mobile/react-native-get-random-values.git#91140c28d87d6fa4f7303296ff2b6e214a078eed", - "from": "git+https://github.com/wordpress-mobile/react-native-get-random-values.git#v1.4.0-wp", + "version": "git+https://github.com/wordpress-mobile/react-native-get-random-values.git#fe4994554df3a16fb9401e28242e6c81a7726cc8", + "from": "git+https://github.com/wordpress-mobile/react-native-get-random-values.git#v1.4.0-wp-1", "requires": { "fast-base64-decode": "^1.0.0" } @@ -53410,10 +53410,10 @@ "from": "git+https://github.com/Riglerr/react-native-hr.git#2d01a5cf77212d100e8b99e0310cce5234f977b3" }, "react-native-hsv-color-picker": { - "version": "git+https://github.com/wordpress-mobile/react-native-hsv-color-picker.git#0b0b717c7f1c4f453922be8c574b0cf638b8f104", - "from": "git+https://github.com/wordpress-mobile/react-native-hsv-color-picker.git#v1.0.1-wp", + "version": "git+https://github.com/wordpress-mobile/react-native-hsv-color-picker.git#c13b48ec55b049e4c59a8629521097c6938f7992", + "from": "git+https://github.com/wordpress-mobile/react-native-hsv-color-picker.git#v1.0.1-wp-1", "requires": { - "react-native-linear-gradient": "git+https://github.com/wordpress-mobile/react-native-linear-gradient.git#v2.5.6-wp", + "react-native-linear-gradient": "git+https://github.com/wordpress-mobile/react-native-linear-gradient.git#v2.5.6-wp-1", "tinycolor2": "^1.4.1" } }, @@ -53431,8 +53431,8 @@ } }, "react-native-linear-gradient": { - "version": "git+https://github.com/wordpress-mobile/react-native-linear-gradient.git#875ec02beab4d1e97e5f4cca57ea3e436f4e8a1f", - "from": "git+https://github.com/wordpress-mobile/react-native-linear-gradient.git#v2.5.6-wp" + "version": "git+https://github.com/wordpress-mobile/react-native-linear-gradient.git#7526050edcd00d0ed84e8bd58986b8108e3b3962", + "from": "git+https://github.com/wordpress-mobile/react-native-linear-gradient.git#v2.5.6-wp-1" }, "react-native-modal": { "version": "11.10.0", @@ -53444,12 +53444,12 @@ } }, "react-native-prompt-android": { - "version": "git+https://github.com/wordpress-mobile/react-native-prompt-android.git#ea43e81db49aed3c284cd1decdbe2ac2fdabd58c", - "from": "git+https://github.com/wordpress-mobile/react-native-prompt-android.git#v1.0.0-wp" + "version": "git+https://github.com/wordpress-mobile/react-native-prompt-android.git#09b2fb1abde2cf2ae3c16b83852689ec2c86e76f", + "from": "git+https://github.com/wordpress-mobile/react-native-prompt-android.git#v1.0.0-wp-1" }, "react-native-reanimated": { - "version": "git+https://github.com/wordpress-mobile/react-native-reanimated.git#1e482d83d83d5694dffa81f333ef570607d7b303", - "from": "git+https://github.com/wordpress-mobile/react-native-reanimated.git#1.9.0-wp", + "version": "git+https://github.com/wordpress-mobile/react-native-reanimated.git#4f4aa06ad3088215e6bd681a5060ee5ca8ea7bf0", + "from": "git+https://github.com/wordpress-mobile/react-native-reanimated.git#1.9.0-wp-1", "requires": { "fbjs": "^1.0.0" }, @@ -53485,8 +53485,8 @@ } }, "react-native-safe-area-context": { - "version": "git+https://github.com/wordpress-mobile/react-native-safe-area-context.git#efdae6b42ecff783f9b30cdd794f87e335307aac", - "from": "git+https://github.com/wordpress-mobile/react-native-safe-area-context.git#v3.2.0-wp" + "version": "git+https://github.com/wordpress-mobile/react-native-safe-area-context.git#12c8baa168a60bc6c19924f6665416cda6293351", + "from": "git+https://github.com/wordpress-mobile/react-native-safe-area-context.git#v3.2.0-wp-1" }, "react-native-sass-transformer": { "version": "1.4.0", @@ -53506,12 +53506,12 @@ } }, "react-native-screens": { - "version": "git+https://github.com/wordpress-mobile/react-native-screens.git#218b47b8ce22ddcdb17f3278cbc2955a00e22a78", - "from": "git+https://github.com/wordpress-mobile/react-native-screens.git#2.9.0-wp" + "version": "git+https://github.com/wordpress-mobile/react-native-screens.git#ff79e1dfb50d0a8de5fa2d5ce0322e7bb2b8948d", + "from": "git+https://github.com/wordpress-mobile/react-native-screens.git#2.9.0-wp-1" }, "react-native-svg": { - "version": "git+https://github.com/wordpress-mobile/react-native-svg.git#44af900ff8cd37a2c781029feca0cb18a22dddac", - "from": "git+https://github.com/wordpress-mobile/react-native-svg.git#v9.13.7-wp", + "version": "git+https://github.com/wordpress-mobile/react-native-svg.git#49e0d3b47158e188093f6bf9f76970e455988c2d", + "from": "git+https://github.com/wordpress-mobile/react-native-svg.git#v9.13.7-wp-1", "requires": { "css-select": "^2.0.2", "css-tree": "^1.0.0-alpha.37" @@ -53573,15 +53573,15 @@ } }, "react-native-video": { - "version": "git+https://github.com/wordpress-mobile/react-native-video.git#c12d84e7972f5f763c336237b2003f0e6717051c", - "from": "git+https://github.com/wordpress-mobile/react-native-video.git#5.0.2-wp", + "version": "git+https://github.com/wordpress-mobile/react-native-video.git#8a286f65cdb85a496793f725e510bd0e084b46b5", + "from": "git+https://github.com/wordpress-mobile/react-native-video.git#5.0.2-wp-1", "requires": { "prop-types": "^15.5.10" } }, "react-native-webview": { - "version": "git+https://github.com/wordpress-mobile/react-native-webview.git#ef42e662f4642cb1c236fb0e24f6608e519cdad1", - "from": "git+https://github.com/wordpress-mobile/react-native-webview.git#v11.6.5-wp", + "version": "git+https://github.com/wordpress-mobile/react-native-webview.git#ba81efba75f88c436b24432acdca012a21c136a5", + "from": "git+https://github.com/wordpress-mobile/react-native-webview.git#v11.6.5-wp-1", "requires": { "escape-string-regexp": "2.0.0", "invariant": "2.2.4" diff --git a/packages/react-native-aztec/android/build.gradle b/packages/react-native-aztec/android/build.gradle index 7eb7ed9b71f62f..674620ad78035c 100644 --- a/packages/react-native-aztec/android/build.gradle +++ b/packages/react-native-aztec/android/build.gradle @@ -1,7 +1,5 @@ buildscript { ext { - gradlePluginVersion = '4.0.2' - kotlinVersion = '1.5.20' supportLibVersion = '29.0.2' tagSoupVersion = '1.2.1' glideVersion = '3.7.0' @@ -14,30 +12,14 @@ buildscript { aztecVersion = 'v1.3.45' willPublishReactNativeAztecBinary = properties["willPublishReactNativeAztecBinary"]?.toBoolean() ?: false } - - repositories { - maven { - url 'https://a8c-libs.s3.amazonaws.com/android' - content { - includeGroup 'com.automattic.android' - } - } - jcenter() - google() - } - - dependencies { - classpath "com.android.tools.build:gradle:$gradlePluginVersion" - classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - classpath 'com.automattic.android:publish-to-s3:0.6.1' - } } -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'com.github.dcendents.android-maven' -apply plugin: 'com.automattic.android.publish-to-s3' +plugins { + id "com.android.library" + id "org.jetbrains.kotlin.android" + id "maven-publish" + id "com.automattic.android.publish-to-s3" +} // import the `readReactNativeVersion()` function apply from: 'https://gist.githubusercontent.com/hypest/742448b9588b3a0aa580a5e80ae95bdf/raw/8eb62d40ee7a5104d2fcaeff21ce6f29bd93b054/readReactNativeVersion.gradle' @@ -105,8 +87,6 @@ dependencies { api "com.github.wordpress-mobile.WordPress-Aztec-Android:glide-loader:$aztecVersion" implementation "org.wordpress:utils:$wordpressUtilsVersion" - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" - implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.cardview:cardview:1.0.0' diff --git a/packages/react-native-aztec/android/gradle/wrapper/gradle-wrapper.jar b/packages/react-native-aztec/android/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..f3d88b1c2faf2fc91d853cd5d4242b5547257070 100644 GIT binary patch delta 22808 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 delta 20005 zcmV)NK)1ig$^*c%1F$Or3aZ&=*aHOs0O|>o?>!llP5~5?9@GYZjaFM%6IT@ej+ta& z90g-QgNlPU5-y3g)>g2zO1&TfEdgvq+YZSgj810K$;3T}Lh^P8=451Lj40^Byo?0}XR#>b#!kfWj*MIfZVGox zV!0)j+Z}jU!FzaLhTef?vCS(ugn|st5IJX9hC9I!N+cHty)Km8Rina?%-BvbU3Bz<$Y0>ZqLf+3lDh1@ zi(FJZz(dMg@JxtXs8aDEK4R$J6kl7uL$s^-7@ttZ0`!xnUEzX96`$g0kZ+w9>Uq;x z7P)<<;&XhV;!Au*X#J!{gQP}NL$`<$%Kwpyukj7ldo%1@)pCsz-zX5n#Ywwr7BtIt zHIpiT?{dvu<(dyn3w&x<&(CRw6^IK4)xcP;3J==g@ycLI#kcrQr1m|-;Qzc`4Ewk1 zL%KnmM-9n#EwwgE*tHktrij`^vhjLMjW-v2s;-&YqM0Gho|bY2?Gh_;H~X;S@>26f z3_P@2c(<6l*L8%L=5YmeV4lWY@;v#UNrfti;`PK zRMfn{r_Cz;Rk5o^U@- z(5m_h7({}e)U6mIEiz^Uq$iV%4~?v0$Lte?a?&4=a-q>0!Zk#)>yT^cSVQNSv<@XM z)vz-zMb#R1jfLak=x);P%7voc*&6nLj78!RMuKQAG)(V%Z^Wg)5PK}lenSs~NKW#S zJAqDG`zZJU!f_D8^wB?!eq6#~EI`9;!djpck^B`u!FuvyH;fSv5XUG|1SCSg8`883 zk;Mg^#7h+AG_9xbGJzI8PvaHRI#Z{@KYNwVUL#3A*mDXd%NUT+Eu+`_56S3%lIiyg zFy>{=Fiw%^n^R}~XUZx<&*>-V%?(HQtzmx+@tKjQ6QMIwk96oq93JVBP6?7~=!+hx z;oxIL;^AK&N$jWR|2)B=T(m#nY8{8yp#ABUR?yQ+sR@!a0zFEwPtyJj!4`CAq@$r5 z69iajO>Yo0?a{$JP`eR&hM0^EHyAtcFX_=W_B!MIf0K;}@dd}_?x`D-8xBH$PZL2D zJ+m!rUA9yn2;J0lWIs%62sHbPTDogPBWca`j1S|L|=qx;t%jg z8Sj*W4KzjfVQ1#vbIv_?ZsynT?>_hmdy5+gJs-y zkbrMv#l{_m@n>Ni>gNmzKflF)kSxoZV7OQbWAVDZyCc*az7tWztH>&kwzvw-xgSjG zM%bds_d zvjQcCsk+b`MDIvd8_0z+W?1y|mG}Gu4`QK%;h>U@y9^8d$ik~7)3vpKS7eww2gu-T z%C@SC_0aU5K28;k4;N`nlEyin7$zH9Hw#VE@7tD8HtxA7AfQY9n>gk&z$A+{R$ZFz z15@OojYkZH|GP|v?1`~ciJ6g2Gh}+ih{yF{v)j^Qmtn%pMM*;HF2k~48GvXN#`RME zY>45>5a2&jGpA!@Ld$Z0gR3>AIGITL`Ry`8Zb*skvYGJoh&C}#uf~P>60po5K`($# z0j)FxjIA8N`brxM8Tya+f*)}SW@3IG5I2mk;8K>#(jJe$A{005jF001EXlcCfdlaJK~ zf1Ozgd|by_|9{f%zNgjG;q|$`vQF$+)@eJA9m|OmOTJ{wlB|{F%68&BNl((+t6k;o zTiZ%XLrM*$C4{3i&C#Sl+dwJcwDro3+9m|*K!I{opyen~&QR_aXj=C_vxj!2tw`%% zG;ijcZ|1xIGqd^Jw_f@TfSvNzAlBp8e}d@2XRFw|p_NlOUGkPlE{I&w_X!UsTgyQq7;6 z_=_OkkH1vSUm5ta`u=qg&*5)^_*;BMHGfw{X@76xAA!v-(9$sW7F|5ML1c@mW*+{7QfQ#Qg6yK z15X$d3d(X>VaiIi>ncN58?wfff3PWQ4OwT(`XGj6gDD$Lxkc?8p(e7)lv_=?&6Lfi zY%%3_Q?{DYpf=cMNTVT50;?;LaNN$gok}?=L8#A7UY^a`k zd#dN$(4qclS8os5y3gAe?Y6j`m}rZ7ZY(jePf*jDOr$(J;SJgGv|~!Mf1tLnzxPQ0 zp=k76=TUAVkgiJQYe99#;NioE`p-qXP9LfS8b}JnlM@pT<*n;Zx)W^^u00la+Ag{F z^t9u)b?ZrrF*xqAryTm1y&=a<#gYj@{j{5$aGg}DJC^dCgxaU2+&%}BmlE-$J=V8? zojV8ajwNE=enCgW5*jQve|<4!+mOK5nH-~%b=|Rq)03VWaohoWBtx@5)JuCEE_i;*OSJ*kfZ#HKt1`E3;(GNqMnEe@<3y=~^bhq06Jr zw3_7N`n=4pgy*;kJ5J@&ZhXP6-CS0iPC4#@2`87S4E#uXd|YKr#hDK3lSohXJ4*K& z+D>nI-A-b{n`A8WIo6p>DwUQnM`|vRRwc; z)82I2qthLGiqjP_e=c8HnC(i;Pa4uo+PG>*ePfCu0x4YT>-Z@l*z1e z08&5Uc-ckn3CEjE(wA$C_*`c^PHAn~Ir3YMX3p~(*`Zqse^0$5=ebBlUD59Bbr0EY zJf^r-7I764DbKj4h%ule%g*Ye6&fdD3d%G zO{U#ZN0k_Je>OF3u)C{#3c*gkH_e~Nza>ZomOC>G&kf< zOLpTUg4QMAY4hT9hjL_(A$M7_SK2MvCwE(NkLKcCP)&y=opR8^hwxzwFJX=@P>Q!`f1g`&NDfjf8bZqOIb256M`$J4)phQ^&E)|rkH4vqXPqd5sey=QrL(jFFJ0-PEgyFGs>eP zGLH-qFB!=rbA*c`N3;VYV?2o5*hpIOv_|^k4lzS5OT}1Gk#s>|w3S(?#3kL>!#R*z zy|4y4(y_R%&_Gr_<()|jKaY=C5>r;5mkXA}e}(x_uhzCwY`nEY!;~cnVW|e^!G}P< zpw2CsmWOh=RJ?X`VMT2gdrI=A;=Kdl9aHD{euICTbS2rxmd!NU%I>uE(s!v zdb#!TRJ?U0mKbY2XnVFdGwl$R>3w|~Et}>BURJdZ9-HnA5p;gDejZw}DW_=9`}4V` zf4p5LFsaC;m^ZmZ;A5#sBI!j^>FMbtbr_3~HbeY~92+{J^Ys#uEL$?Ixsp+}#RI66 z*q6gS6}Zcm%&02VK-PLO2WwVtl!L3f>~LzHVkA?oSriSjS3RR%!JVGofQ{i0)3wN0fOCi_}e^%!9eBI^nhKOD6jHmhKkJVyKNEERb7jt(> zf(%T$$xGQw*Sg}0kIp1K`*KmJSC&1xO7m}qcSB06W+@PlX`MHt&#)!U)>nafdltMM zf+@#4=#1OxI1_(e(Q#P9r}wB)Vr`eitn2FYhu!>zFEDjsEas;4wevI!$xCW~e-t?9 z?|91^7GE^O4driKYOa>%CW-^GcEO${7q}3u>USPW^L9G#sI6u0Ipy!vwY0P(zN?E& zExzt$??j!Yw@}*N#apJUuc-cpGaYJJUy>5J+iTiY-pr3nFArI&dbY(i}uOWx4%3bo5&%fhye}&Oh!vsZcFJ9a^X}eM7+r+3-a$!24xmB)Ho2KvL ztwZhdClKE$UOGh)i3w%v@&)&^W5<-v{!4DmV*(oVZC96~RPt#``e;0vQr9NNBsx0j zD6BEqKblN=*o#yDrSmI*x0z<#Ij33XGac#NBh;mrRjHiBbSyj$L z^$u-ZI!6k~pM9n`bS@Puf0cdn&yv7+(w(xs1tyg7R2dU;T-b#5=z+k2fiPk?&;A7f z6^LUkrjRI%lN?VMjUPfty&l*PsRxAqrgL9DBlr!H_cCVKKFrY|{P6Kx)z~D>Ewhjp z^)`=a#tOEZVB%K1mA%F+BfbxB(?9D~X+ffUN>qjJDPfgb#G^S8fA8ds`XO**<18u~ zo35d?kjbYz4_#2zAA;1Y^UhYO34Q!^gE!^*R)M6`Epn;Cqh7Ht0>9Q-kV?mdV z1zk33Gb?n@)4Hgh(#l6FA5l52dbO6oija97RX0#Ohv2ZxqWU^4rAwvOrB<(Rp$}TI z9NV>QE4wZy`|X-nf0mQ@19%5TWW8Fc7uGdrP?JIJsm7+}S=7zjnBDgd?z@ZqJN3Si z?2>{_b-02b)UxXEL)wc!%)XD5DEsfq3#;6Ofd1L9h7Y55f75l;XRxe2Fo)3a9F`AL z@QPWi>-pYzq5kv6?Pl({6-)p>Wv9U~Sl!!Mb+;f3gOA%4|2)Xv6Mc)t>6A zJvCu}*vw$#@b0RL=P`91w`34`3M)T`O`%&exNQ!bheKOtar?`wYF1WVvG>%hs@C7? zRn;r7b*kz;&!MUD6Q~Sr%b@X;COUhnNeSFQNPU`C2CuBD`6QYGXbGE@E2}bSe&Oc3 z^_rFpTEqSue=x)T4BA?5pplgAFW|QJy7Kdenh)2#{Gv{}&*OEv>~(xqf3qQdFVhOx z%lUoexQFiF&jh)b)ceqk1K5cU&UCUph%OvPACA!BM=`|F7>=>}jx(LQnch7NOD~=v z$J028527C*CFjR6fLC#fvQOg+ID;?YEWV5f@D-e+e-@|lb<*CzSrI%Sew>pk*kWNs zr@)U=n_9ercjHGG)SY-1k27%%O1{FmCzvh|veti$e^r$FHvBkyLCSmtKY^b_HFdm< z_pnz(YhJ@o(N>>IjC@M5mrE)3vME&|)p!!`L#3#+&aUu_iKl3jUnlpgsJh9GYYeP6 zu*1MJe+Hg4@O}f&8F=16zkw4FALZO+jV{F{n(G_rxJgX|ix~+~H)&1D3=~}qeBdSv zu71%>{vR3G+@w8a_bn_NX`%8!#NSZn1k2-e~nGE*xS?c8hkH?+M6gVgMClK(n)+b zlejr_&m8s-&*I+DeHk2RBoue>n?Wb5a~_Yf*qEdq({$BCbYu#viE|O6-aW*)n09NCW-7+^XHcj4zWHojeBcEua0W{g%8jMz*jzVEY8DRBx7aOQEk<6s7dPBe!O ze`jzcbhPr*=*r+&Pjl$F8h86R9!U_`2DI5^imzdk zx6-V=#LJT`t9};LBunX07Sm%aB;~KOfAqi_a{L0zx02kqF=`*B8}^d=OZa6*aFRaG z(jH^fui{1a`Uw&rV^3lB;{{(ouKmg@2<3kqpP-J)!%e8TN%56BH(3hTR7yv0@?7y1 zNF-<~mt-)TJEWfGNCk68Xqbo8iO^}bJE{f6iVl zWXvjkQofe~e3H7qk7e`}VeXltOxaP;euvIwUSX*5b$y~+1d>k{GNl^wO*CtL`#Jd% z=5l&|kwR2r-XFT38g_>s(Au6;+J+uv+wKe5>f;ZMs81j?T5swAGyi?jVIM#K=rGeH zIvfbIXM_XMVY4YZTpws=W3)uCU1My%3bR%4JoWql)UTBxmUNi9M_7GZS$)d3qgjP= zwgm{tpVE=B7>G}6+d>3@&uH7ig!-5D4I#oRdWAhd_t}kKVJ|?=SGD9{#e}{_RbX8I zUriJ0|3ywB_-(VTAEFfsa6sYpV+Q~L2@sQSG#Zo8Kn9bb*9d=|SNVS&WgULr>@m~L zgrc` z%9W4Gm5-_TxK#N>4EN!aa^+La_%uEv1@4#A&o<*QKG%$Kd|ozRQ1L~%{G}MajIYFS zr*xLVS7q~ng0HFgx{3!?d_%=IW9Y=U)i}b>YKJ~9WG7_v<1#A z-Oi9-4>Zdp=pr)itsZh`v~O9?K#ghsQ%6WM)EKez*_1iYhTY8~jaC+?$Ct16Z zhYr&cqtu${tPgI?o6c85AIi!I3yxM+<@yioJDGnm^5w9^rgeA9a0Brc+c2_)KIepO zIeM0g7?dl?YwO@dVlz9{NaTkHvwBV@5_oST=0tY~3rmbhmf0 zKn;HY@;Xy=UBmWLy}VfIt^uCduv2t1MsQbJIUL&^k9dEo!F&e557-ZwXQV$0Q~}2*OTkkqG@FfSHlnSBMndC zG{f8NOlf#p&iCNQ8h(PGYIsIAKa*=e$FqM5&S-cJ&kIDl^SbM4_=Vg)i&=WD1e(S> zq{Whga~kGwUc(Expx~DpeuWn`yo8rE{2IT}@Ctsb;dj!)tGJuo=rb(Clj`Iduhel* z(a`Vl2L*rB@EZQ4;dT63!(Z@M3O67inbYeOt!#(wcpXLiUNhf8=5%-tJJBtm4jF%X z!LfU2^$mHVH}N+Of0zDmlXtXwsVt%G`j88(Su*C8NR%r9tKdS8GKc3E`aOenz;P=l z^ZnGE?3#;%Bb73)p?iK_32bjzxEhw6Md=<&$ePoVGrWVkJWD`Mh4Vpu+Ne*B`Qj>V z+f4DUM1v}}XsOISDyp6nED2nnXjFei>&s!YS?H^f!-vb75;Y3}&gI0pccS1}Mb9{> zdy~8vJ(DpCtos{S`O}wO(Hk6N{;pOvFg9Q86j|s-T$9x|vG76YtbYrmS;>229_>bn zws9CMXdAwjX(yNSuXRBf%JpffFvKrvjCX7~jLynNfgPQPyh%dddHIn0D>r|(<0APt zf1_%)I=rs#N*9Jk;!??8GWL+ePmMZaNy=1UZ~ot~>y#HiO{!T<-S$N7ekG+TqfF|B zLE|K|Gi>`^1;EV`K-c8}Ao_KenBPH0)V{VgZ~Q#}Dp`Q-?MJ}kBgT@KDgbsfBZic|kh_<%M2Nqzzt=#jO^?Saw ze$U6&@A(?@FF}aEJ=ja_TR9p>6BPD0CfCnGByXBUQ?hFop=3Nfi*Pa?nMEWSkIo{R zJO|}DN;aXFZIt@J2K2FQ=Nc_wA3gy1Bk75+n0%?YM?Xz(BO?8Xw=RD`J)As?rV^H2 zKw&L$WDY5s-7pr9oPiL%Vn~ee?^)PqhmBSKpU-~;S-PDpO_Q5P$jdA_ zIZ0MNNKQUPR-PqOUL{xFAV>Z|&3Diz)?lAlhy5an+e9yJ7ehEm%V{x&0r3C^#j`jd zp2v`Q1;gTX91?G0)Mw!lETi39@Ihuln3mS#c8;RdyNmt@$Um~L%+Z8+27}xcI3iBs z01lF+S&_#bL>qr1l7C_d!wA#I3LK(b1S8baC?D*N(!&^6Zh-m@(hAg;J>p$>LcyKy zVx@w^3daA1-eU?n-zKoTC>oZ|TK6&~?haA{DL+NP{3>DNnTDCA1p;N%RWocq@3IG5I2mk;8K>!U@s4Rai6aWBpD*yl>ldx$P zlg~g6f1Ozgd{oudKPR)i$?(_$Aq?w?1hR)635bLwNHhsZSd0|mW#%OrnI+D=Aqll= zEmmu_ty?Wx*Q#ixRZtQjifh$c+^VhGO{=!ns-L#~7X6B*|MT9=WReU5@+0@Xcb9X| z@;}SH^V}B)4-wHE{>V++dAKwqq!}sAC}~D#f1}BfW{iA}byFedDm>0c{OV(Fa&w-H zjhDvb<_SDenn`Y+%v0QS15cI4tMEx~8q3pU{>chYcX7U(9^e@Y&verSE^yNxE|i`k zX^Istann@Jb#p0~xv7%N<#U!av!$6cj1KZ#h3C0=zQPM+#zHsEL zd7dvkMHP;@sZ|u(%EmDInB&rHQ@F!TL9UgiQzmvPyj|h1yXkzH+s+rrf^Uet7rN;a zzDPbVlDCV+G#4rSO(wNA9M+>%K`j>3V@#gvniZAn>egShK zT)UDfr|vv$n^qpw!mZ_vMl=v^UCcDRDiV$vTG&{x1>?GlFJW>9Bdx7^lxbpJB-&cu z8rA$ky}To;wYTfh@;Y-6D_#CbM>rVK{7h3aO{}d>jLRtT)6%&3bgLhC#7F#HR(k*3oWAuBITkJF@-OEoT>1*NkJk%f3}YXn&a}l zE*fMSVUZ8(M)|rmwV0BdKBciun=^kwV?4w(Iw+!7rwuCnEp*on?q-^IOf63zvI;vZ zvU7DHnqsP7X4TyMoItyLLzlpb-Y&~x3h#hfFzAa1q24rxrxgsOQkcnmY;Afc69@2D z3rn_`iOJUVnC^>5e*;EWc|EWQAXW!j^_U?mTg2$OsXc1L?QsKibuENZh8mpB z@s<{Wde+9}@V4eISYIoH$6&~Dk%?hiyEf9EJ`1;&HrbpcZW z;|BUdS9{VQyo2U08Mxch#R^}B<=ZUw6P{Vsru(+W#BTEohBACif#9@C$g&XhtNDz$ z7Bo?i9gD=HKHbFnFuk)~_Zhn19B~CLxIsE^W~lT_tMKI@)fi|EYeqb(57qJD6+>i( zrDM8L(+M~kqNde)e>4<`#RS4|qQTT4PL{xHe5&6PA# zvN+qX2XzUa(G1GML?sqClLc7Dm&?}%*`hk&J7!}>uLr0VdW*>s4{r}Z{HYmTCfytk zJ#0j~QWi0_jiu#?Ni{MeK?>$b#Q^b-B#~8V{SxPdR6vq_UK+8Qa6F`^0=3O#%kI}D zTPWL;fiG|9f9@uS3SXh{cNM-8A>J2h?@9|sOl1WbgH&erEa*XVHWOU7plH#pncAH` zYt}5Lx{SFindnY9^kj9;l4iCvbNaWMEn8(ylgX_zCcad4lO!}p2rW5rLh02{lGfZ~ z(>g{V>8CYMXqBD_t#kSp&zHq#9mnDm4We0{bNhE$e;~UoK4EjGyG@eR!V{KO7B`x) z+k(EDm{%s#RC=18QRy9eSEXKhSf$_7A5?mro>1u$`j$!;(>GOmkRDR$a=r>1pHQhO zi@vAQx9KvKb`Y}e_f`G@U#;>re67OQ$;b67|B!D``A2*M((%!Snm${I?Ns?jz6m0v zO9;1ae_UBvifTpWAM?%d?ex(!M+F7Q%D3>XD&NMpt9%Fl1kojP*`V;9D&NI-tGtWv zQTbl}sWkVgyqm98`DgS7azX#fHSw?!2J>Z?0ADij*NA#FC95K8o zKMgGq_G;lSOp79+MkJb*d215c)oVn&EePaZf4vilIN0T#otoEGhEk$`|5eTBpb3$5|w@urodz*DV>@~DdyQFPzN5E(+%MY6cc{JoT+B5@= zf9{=`vD}{NZI4E<(CG3)(_ONc1+dZtz{(Qi5Zfz7t2YpXa-t$54C9w2UM&jN58!<%g5e&?{A+3|ZYNjr$VyTZL&T zknvWUMc9x5m3zdE_N#n=4=UWN^21{Ie@FNbvUPv7tc*srE(w_`KT2f}*NOJm@!_7_}&zBUy}k+xx3gZ%ZUv;gzWI8-;(X@@xD6 z67lMwuEhjSUODWF>%q2gtU!wiwGJ(8h||R}M_`t4jCHl}cO?=l3!{ot`E`Cn;oqtJ zd;WvUf8;-5tivk!RDP4+Qu)vPe>Muvj3tgr@AEq7Vp3l|SICRQ`}}NANs)tVfBP>=B_4sxsqA$wUj1GvJA1mr-5?<^%`>E?ul_ z4*`ckKvROS4-(GKaNaLGf5zpD9oX{=zBVo|tOa-RcE4swNresza!!B3H|zz4a{V%T zU<@^{Acq-|mHjs{xdpWuvE#%MsMTmQF)e$E&g1|)v7l<`{M6-5$XFj>heq;KF5?4af>k_}HGw*$t zoDgP)+#X2~s!v|1rI`{j-gLd;30F^k4-C9k?_#;rNfs>T@$a}?B6%<6IqLCV?j<6v zRv=lOD3qCI92fn?NpY;iC~;bD$<{TdeqTu&SZoG~x=072e82TYJ2bLQi`7S>dQDId!3F^Su&~}~Bt8clBjwEs)MeeL zIYV2mdtFaIjD}nTm8Z)(;I8Xvcy;)K5z&&P15sP2lW02?5|M*EbOC*Xm@dRu7F|R+ zaze*@jvUv`ei+ai3lrwBJJ=;U-J{n$B zypNQkl6~YXD&0pT_Lw_-7wrUcqMe47UK&d$gNNxfh4S$>gRaC#kwufPqVExzZ^9Fs zZ^BiU`6hhX(EEM*0eXa+{p2PE&!xrPG_oGesD`44e`o|=MpxK9_HN3laL8j!g%kb5 zJvm#lc&CyCNvfI(8LDY0{iGu^suYKk!#Pol_r&X9Njc&fj!rLOW!9Y9)~R# zLQdY*_ijlyO{svCQ=59oTcOw%xN=<{=b<}j)@bVUICEWdFWgTjRb+dzyJ?#JHX7zp zM$PJ`lQ(!2>6*S_hl_Xhz2H&0DPPoLGu5(!e@3I-1h&tmk+d1m*a8!3G?kiZCi$Q! zKb=CYP)C4Hr}JnHZN-crzCv_9MW_pX7g5wyVG9J5)we@Q*>ncYr#t8;1 zgp%MISalcO4dslaZM2K-0Y5nuqkFN!4jMuFDcuLPF2%09@#e&HDgBIo4l~^kI;G_3 zf5SAVLfaL}Q|JMO_OL>GiKcu(qw%89R6as86sr7;h7YjGgY-}WW4{71L1#k|OyOuK zJwP)UW*y&4Gn;Y>?2k}kldYt2Kfxi2AH`@1!pW_P;nKmwwgXg_MG4H=(=gY8wi9A@ z0q0)_;x3>ncxb7*q;Bega`vNFH5Dg42hbyG$fm3#G+m*C zQwE6GOAjYRdArQnNSY%u!59iW{5k=$PBsIKHrzqOVAQdQC?3R=8Svk^c%A~^Sq8tUe}#XCtY@i-6Ak}TS= z!vXhrv!vg8Q%r(80pJ^9y_={2e_YMN_KWa-8bF@3U;$j{PSf+TeM*|jge_f|FBZ&7 zS`p}t+S5yU zO~pA&d+4-!Zs?_DP0mNCvdNaS90tv)f;nN;>c$?bvEt?m#7!9V^qsV#f0tG^^-^t< ze4o)nXZBE?M3{Ogu%SW`4XtXba6L_V9wleBg?Epuv764?fOsTsx<3g_$aYf&;=yu6gbh&S7TSZzv=>q3A*}BOrEX}e2XO2K zwfzX(2VjnaFx$hR_6Tru2=4!wX~cE_aswRmS^6b(y9LSXIWsi0(PTOd__?s#8hV~y zfUzs+OnTAusw*(}W%@Pxu7_D)rdLcjA5H<_Ffb_q7=xXEW5PKXfBgJ51?L)ax%#lL zD`|QBuT*H6La!;bQlWaHBQynleUg{cClM`IsPPPi)(tNN+1KffLYLJXBg(C;CCE)D6`ANPnLG#Z>><14wHwJ{+r7gk z-iE2O`&pW1<_gjLVQl<7g31f9#fxyVmym~^r#aA`us9Ffn|2TZhLi1c8llkJJoz&a$&#No68ZUMe{3#pbxkj|N@i}eU>%UG zaGqp^0A98-AQQA4D72IEM7R?92t&MXioh>k>7{l!)%i^W#(F5)Lot*p9=miI9%m25 z#lg1iqT!aSZSyFP?&`ZvHtmp3m-*&#J-P=%ZbF)kg1aag=F<(JO9giss<+Eh3Tyz_ z2$pd}HKU*ke-D%~o!*A>-l0<==`wl`l->ue50JV)1f>sK4^!D|pqJ@%HvNVE3XN?-VelUP4Hh4Ty!Jl*9Xms3DP>;+idF`@26P4VSL4f? z=Y~^$ME?b8#1tASpVKIXK31sp2$d@o?4#MFq~Tmff6%Rf8D#ceXzPCH3c87 z=1?Cj=NPmSTO>0@BiQ&S{VS0vZbqNLHGi}nWmiLSDax&;1@@b0L`kVxY<2GH`v}17 zLa5r-pYg0*{@-Z-5Aps}RD1tM#f#)ipQk_xqA5+}W9~hsCi3ZjpfSniQ_Z5r2FT{o z|C)u)fBmM9A%`RW?>$1f+|TqV7k2tI!E_B)iKdmJV&rgFe_87^x0qt3 zWnOIsBxg!0rzI8WWU(z19sBMRq+@4CLd~n8RUHY7E~puY2-}{Fl&rGNm7?SVC9808 zLC;p<;$o*6>C-gM3cE6zGb{5pUvAEu(##30a5lR$DT6c9K8i9Zi-*a4R#B-+j>tj~ zw*K9~lj%pq{{gdrRk{KVZ*Cb8wgCVDU;_XEIFqnx7L!0)4U_EH7=M*gTT2^36#mX; zv#aS= zCG!2Gbf`4J%e!i@`G1zM-pF((?r70YWPG7Tzb|$CMdaQ3U?9($iPVhqKB!dX9|`r^ z?DlEW>1gYO;2vacNmy*CR2~n{no@rg3?zh&tR<2Yp_PdzN!JJ^EZN#2%h#$o%vF{W zf=_8G^+6(-np@t@l(zZL5PnJ-aJQ07cD z#$t&NtYa3Riz=Je$;ZbTDq1j3zvN5bWLuUp@eK z@XXrhtQ)M5xbm9cM1KHKO9KQ7000OG0000%03Ax$;uZ%009y_K049^LX%>?}S`~k3 zV;ff$J!4B6SsurZVkfm@7sWBHEZG(bG(g-2yfsm4*}+?J($*bY6L}JOq>e_34P_~i zmVGHuD3r28*B*CUi}$dH#|Lxm;V1z8kTJRN^Q1UcEUMJk2i$Xt#<#ZB41CBvqQtq6|c6A^q; z{)^+8Fg_K*r}3ExbbMB%XH|So=FdlP5?_$vwu9X34S5)v|wM7Ayr? z+OiCLBCnT9MoGbmi*sX>(^D&p^HXyxmu53lEAtC;>6wcPqSM#)n|dm*Te;Lc4OqER z1#J@rtK{gGv!v(ChJquP=Vl+7npmivI+C;XY~ENb8TO^ZhG=+Z%tGp6GjGsD=t0vm zoeK(@$>jg1(wJ1YgK6>9#3re>32$n`GTTU9fX04=Q!b z){8~MPF>cW^)Y(2K~0-LN8|gU1+6`2IQ!$V5^rSdF>j`~*UVhm)us+WuzT>=@-(yS-8+Jyq$u)UQke{khXSInY<+4z53v-d9iY>;`i zZ09fOrFBY-p(owf0HxvKwhg0H(sRb7nKMd`f<8~FWUQ5K)7eU8_Wn)%;Odqm)!B4) zT!BI#yY^U}+FUb=etbeD7lH`$j=pvyqZj=`X}67y!cAjp(=n`)8}@+ZMoVFIlr&@L zSArMAe%}+za8iqN=|g`)AmLrK^R=Sh)u!>K7s)Ip)Fn zLfKw3WRu0eudGJogoaT(sNusnui}RqCh)R`$MJ-Qk7HIt8q>Vndo64D5nj=-iZ$Ny zgDl3&Wqxc)kRVw3rjMW!2OR=(b!z$b&-;TO7v#ZyQ zHD}+}ykFw?zr*{>!|}m`1$yj2;~RHtt~1`S&<`q$|0FL^R#w6AJG%CMiAfW43cEg> zKG2gJ7+Uh~5Zjo?(O-BR#u|3({hhxN!rnJP+Z!4MC;xv>EArYz+I{lY$mPu8o*&xF z!qO1Db{2>aN<#~ki&@>FxnTV2xG)N3eY8+K?d^2M(+x9|Xw=v1I}7V};g&Q&*U?r! z@+6-%HfOJi$p+l%e@m&ny4yvM$J32*rRV!qU_4#c^Q8m!ys{k~yt2P?w@Qw&;RW%s zU0|x5twVo^Ea4QtlFssrtQp;S0Oz3KgIqOXkn0caStt2p6QmsG9(y9khq!t_XN7Yx zQHAoFt9pTBgfq~G0Pe*{C~2M&K8i8UVqn}i@Gvz+HzEcS$vbGOTRB2n;CEGkG+WT` zS~~7&`<6r!T0&w1lfKRW5=rHJJCUrQxr#t0F;ss=a3(RFtRi$iumg2j{t8#ovV+KS z6|G!p6|_ZIiSA%`sEW?*nmauRag5WI zL9`=*6O8HvhOmiY*R@L?>6&Y|F~#t(R`3iiG8aueb(31>7?u;T`1+htJN#btbq_^pi39OilUH01>> zy55Y`*pFbzW&arE5R_GwI8E}T`>e0?q?BG~GJ3j#froluMliXZZ0@b#z1!|>5l&Ip zvqzb=X`*Bp{aKew%sX2{>%_8)rc&byt`f<|{TJH!wI$yZKJK$TDK>i;r~5KPlA3>k z3w;D1+8*i)JXOK{b@b!(81ykn|1^5oL7$@Zp`NXt8iO7@i4|f5(S@hnO44|_giEu_ zr3K2r5mliJ9e%s`bY7qt3EX5dI#@yCC4>{NqiH)CO}eWNxf{`;yBMxwWLvW5msK>y za&l|yeY=<9%$o;@KTgmmNa9JRYK0<2r0==ilQrU#$kq}?E=Rifzu^|?HKtt3l<{)lOUxelWK({e_J?ZTcA`yydzeGw$KYAAcz)} zixdh$N$}sYtaYL6lIb?SYQhw@y|JPX6Afz*_qk% z^Uv3B0MeM3(11i8ElCNDNJvN_9Y8PcarANA7m@)99D^JWIEEzzFd{+1BaX)$8HQTx zwN{KIe;L}dhM7;~O?joDCX|Af7&F$_Wql>9>FS(p7FBbIw1+iavql&uI^EU()v(zs zWqLzhiwwRoV?||X6pY!;^<~w3E-x2|6V4inTv(J%O`JSN?69Evlb9B3Yqf11ij80rmu!IDiYw_$09&N0T&vJL%gy zIwC-_qO8rx8}_H*c*3xDE>++jYs#(^&)cL}QesInM5?*RAT1c1rlO8(qI_B^bb3Ut ze}V|(LJ%P|aXbxT91|RqK}_KpLz`P_7zSM(d7-cA#+H6WJ+vMt3gRlR3CCurkX;Q- z9|PZVoS?hP0&@z8TC4mh+?o|jj-l^NJyuOjj>XKDY^sN2I!=&2ebZ3wyI0YPMc^n= zoym%#7K@RABvol|6^+s5wCSd$6%y1{f1<+RSzJ~493tEyt{-7RNv%+cIB z6xzF^X3aUYWBJgjwt1(kntRov{W`a`fbu_lFnY*gVES0c%rfR4!j@e>_IcF4MN5yP{Sq>U{h!zUJJ=cAD3_if3PW< zOvGcjPSzaM_wd6y!Qhv(JbD0}Z6d z$KU}3005f{002CbAf+Uejour7#a8Q5+eQ@r))uTIi^RB?gtnv(H359V+>&6MBn6sV zad28Ev?jgDLU9#rIU~zWUZIcBw@7E&At}?O|2osR=-<9WJ3T8oU}A$zCNuq`-97v1 zoNqs!Jvx8>`|Aq;b9f+Q2#Y7^k&zL>B1cY!ge4hSTn^$2u5x@N7RwxeD+2bh3>nur zt_N^~W6owHmsWBlMDC8uk^28sva*DPdS|*2=ndS1nh`63*8( zwYs5NhFG_ZlAy~FS#Fvgb+G-=qSCb zp(8~Qj-Y^2gisZO(g~3&Maoj73J1Z^q)9K*5fH%=N+KeS5Q?s2zee_hA?({;06fkFE zOviFf>)qNmI9=U}bRy7Na9+LbOt5o83 zPBoU@ziZ`(0^NjBNf)F%!?<4Hd-V`DcAakr9PbI=7Qj_0i|*s>r^3ia(Sah_oKIK> znQOFX2hK=^S??TwWO`t;$%oQ{hqWf&FzHA`q&bPq2Y*7EPUt`^0+jSs60NfO;FnS9 zj)wOfOW*kq6hFl-aFR-DTmex3^7AtEJ3VM#yqJlukx3zUS@|t{~h1)*b#ye>wlA;qQw#$z%_gz$ukQ+XvE|LNt&mkIA zQ;Pv+h7XF&bd(uw54e!7kDayK4df;W9KLk}XM7l#aCBVUhUUig!JHd77vlXpbgB?l zIAXJ>R?Ck;RmtC7mAQ*_$@lZFl@|?;H^a-5-ka09$Tp?1Iu2X%{`+^!4~zKu_2*yw z(jBM!h4xooY(=bZ-FiCSzDt#h8roHe&3Hd_yUpliRol#7 zAS5FMLFd=XRym~iW~EM6kv*|eR5vlr&eX@~mNN5oXtM|Ev=|+cg`}BSXf9w2N_AZ` zSdP@JXnKUynGt;H8~0*l=SOt0rc@%`)R@-XmsPML4ZVe3pB*6Qi_AX$+;T9c1Gyj4 z;P=5IyE{8MW*Ej7(_5(U<->qRgXbCH3D?2l%cJ+ja}jej_U(jylU^Ic=l64w=j95v zbQQbjIPr7N_g!N>2>EC~X`eF@e}46SrrcJo?`5Zi#D(%>`Up>^ZI~U}AgeZ0eGc|}$J zkUqY+<$Io#Oqnl1x}_ml%`m$oW9n^h`E{@H63ckS(6bH&`3 z@+GvNuOYPg-F*556nCIJ=&mO7kI^%Vg0J^jrx%2pPkeOP)O_;l{B<=+{|gN~eF5A? zI(Vsy;)h(F$wU{`mw{`iiw2(6&yxjIucHPzkUZ0GeIIL!+oGaNbbl#Yh)OR80?n|1;0J!B3|psn6Pf!>R++GI)x*Mv zhGXk4_@oQ!a*H~A>q|*Vrx)-^>>I^Nr*De4?O2-_Jqmdq-;{Z3>=m!AJQ=PqO{)GYurw&9HxsH{d=NrD?pV2KaUx&rA1jyGuet%MKJj&bACJKtSH@gL? zx@>p3B&(ph-pQ!JX6Omp6!zE)7u)MQu`co^-&3yCj=l0trP@l4W~kUVNCJaZp|f@7 zb(gx0hwkF}A{Q?zjK#IqaH1+Vx3$-vAaeOLl`Yyp6O62q6?)IKtuyBHR-5O_?1iiA zKt482>mKY=_e~Yv*FNoj`-5zs26Z5lnS39EwKWc{HTY2MIyWzAS_%#?ZTDV~@uK=TWCAwO zO5t9KeSUYReglVutdPz$&;lhnE~9*c>mYTj$6zj>&pm6aZW(Xh6!&!AK?kv{7}f5G zF87Q`;708b?d5>zf!yR%$&2D&pWBrrcnVtDk@I7(f_GbJw_V^2_ z{hsv);Z-YPKgj&o(~N!9-ytn~Jl2NESC}47{hH9kd+t#PDdY@6~^V_ej~8 z7saZK7hSpvoRfDU2)F-=#bA={M(#m{I0zb&WY>#zUzor0To3e&K^ccU+a_v$9MwO2 zg*0R4W6EclP=Du6mvGNGL&_EW^5vhqJU>_I5bimjH0n>(h>Zqek2Mxf5;#(@tA-(t z=8Gp_(=hdd+da7f=O^E>kFYwz!{wA(atMEN+(_(x)S^Oss~OX#G`}~+b>XyL+VHE{ z)H3h*`m_4Ppm%wPQ22;uBk0HG&n(k_ZDhz&s^Y<-)uzT^KkU2BA>)buZ1ocdG3Uth z+o-S@Y=X?=r@hNyvlJxIDnHR5dsjQ~>4Wl%VM4bTZTH~&5xiAp&)X6NIB#Du$cbh= zNFi3HIxdv4oS0jR8~v-+7@2wlbI#kmsLTyP-s?#1T*<8+D;X6pNP2Mo7YhvW9b!Bf z)n%{vbBA7hNu)Uv9s*BkSvkO0ItvjUvNNpHl1q3*-Wtx6X!jUL)Fbq{d1y-ikp zk>Y{X*p->aMtyE*@9lve|2Nna4UHUl_q94D*3?PBn}HG;LlE?P14B}StN=7LW|YCY zG9`q@KgUCy1tS%bCrI)ijjHhvLPPTq*A5m@DoP=o1lr(W55W*_m~$R3?y!0N!|jtQZM!ZW#%c z`iG~a4{^$9&<7&}vXi8Na5Shz(gLc`;Pu;l;64TcR#Lob-V{@ZBB7x8TLkdoIw(?# z1RdU<1tR>ZGM^*_5-B-zH>qaR5)zc3MF8T#6jKbuMl*uLuc62zpFfVYfIWl?dc`2X z*;#3zDV%Du-Q@&z=b*?V^SLAC$|I>@@|*!c7ekd>b1KOH4e2;&XmpP7jd4`Sd)^S> zPNB-lc>{neog$?`^#We7n+yeg77zeDlj_nbf`Z6-D9Bs#|BqPmJ~h+4goDbfEg}Gk zT#8Etj9-)nL<%TU0W`z^U6huL0H8$_ml&u}`F84c zrDskYX>wsH1r!C@mQDiO&ET<7QP71V>ANXXp0c7G3c_Cef7_(`z_lfJV80*yHLgYN nvEpR}Ffj;rE}sKLhd|1%F5dI!=ox-rLy2I*tTukw(acLwr!g`nb^)G6PpufV&3<==T?1n{;q$k)>FN@ z`{^ENfgGQLY@!24N~c(^=mrM^!-E6^V@gdT!%kHM#{`nIFq+w$xVgovPCG6OV+t&H zd9YN3JxKVZ2^-1S*bQ<(cY0N@PV zS=>n6=2p6&=jM%efneS-ePI8(TBCZwulM^C6-ZG0*`cuuY)ZG?f^};H825-ytI@mg z>`HgyB7p)H^X5!u6=bA=sOS~4Hh3?eCl+pM+!S$N{q(W2Dr;Bp=)pO)iW#>``R_*6r8#mN! zE1JgVM%3xJJay_{Qs{6!uWOVw;_&wRlt)$O&AMA!5i4U?KC`I1D#;!s-(jq!Tlh>!Cy7!Tn43i8nWi_PKrO&e9yN^r(S?&0C#BHV4NhabM!p7EM2g@sqf=|4(|K{ zJW;H%wl8OTRsd5EANYD@WK2N=GwZFpkIx2N--4f?EJ39&GLm2ztcJtT035NbG-e7j z{F|v;k#uG<6HQ6POmqD)Kh~2ZAl5i24i(#6e^A2(L?WuF+z{?;Fa(RP%KEd5)Qpge z!hbE=(4Slc!9-|c17>gI}VEj2pUxz`gU{j*gb0 zs|TKG*D2(_8S9>G5BAOdCvLGK`UXVE=!?G$R|y%M?5$aIyd93%;{q{`yOwhl&i-Yb-)f8_>9w~(P?`Q-X6{Zk%u*xJ7VQ?V zGC&_rfZ1h~>B9cNyQbHJ21aOy2F+$77I-ZYCz5+{Q8_x47l5Q|*VTqAW%^@X(|-w+ z)_0krySs8W=bX|dIA&V_I;(i)dUTZAW3r7-ItYw_B@`a@lge=nF7P(*k50v>q!I_VefV5F()(xMPP`0E%muv+w0O#cuitgYdIDaLlMsf!AABD*O_8uXGq zHh;x>THO%UO;rtOpwXTjw9&rZ-!k7Nwg`=rl79I9L1QBQdVKa+bwuzZJ?P#2;Epv`@UHq!B>O}?Zqxmo+%wBh?vNQ8e}Fb&VOFYRZV{!#>_~xP zihx@Y2+BP=RLCm|>(h`+ALo++v6P56T>;)GMhXrh!ji(G#61(AgMH5oDe)PCd!C2AoyPm?pr;t>Dpr~em0fv^BraSKZm4}1623tVDg zxyH5{fd=OHwmm1pG>ob=by`PI2M3gFjb>X}y+g3IHFdf&YCUh}5vP6c<$)#SC&AmE zn$cT{lA@9Sc^uqI_S2;1dl4IN>0v0z;dmS{{IEOoc9CY!U7qrEN8q&J-+O*ypU=bm z=`$E8=vjW`PF6tISu{q3hLtip)q@*oalmfqAPiwuc2XDZhHE>(EQx521hV`Y^M}mJ zvPh896*v6=6wCsNgBZsqPS~iAEhr|n^KbgRWnL~pn(5WwMC9ch#A=EAS9S=^f*3BM zbd!kiDMNq!uspV3>q(+KrIRk$IsIY>+K7I`u)W1a@G?vX_yX61i8}l(Bn*~M8yWZJf|M@wn!H*=g?6n4Ochiwbnnm(g3ZDN#TgKw3L-9 zG*gxpOziN`GQxV%jmA0&$B_q^RXUZxnsqT~F+xp>*%*Hpt~&;pTrqHhW{M5TJWeYr z&>yyC&akk30v`;EcPArJ8qE~3A{5&{|WUd$~Yaw z&l_S%)F|Gl#c_pZq!HQf-Z>O!+Sd0Xj@yQoEm(s-LcD*`Zk0?t*t6tmR300f6PIei z`EN2T?=iSm(vCGMpQZx4>l5r?CusT&m=CfDH_$2_=-MZFcJ)+YvULwDOyZ`C7fxP1 zc*MH%@<<(aDmpwaRoUDZ9dKdq!XqqsSlmB3ri+U?$WJ3ynb)5W!6ri=*yViVSG{ea z=~sH=lxv*ubY=A|48`} z>GH3N+WP*oO(OxWP+OT<*0lOTKe8-m6D(l7F%Xhk?uUJ$AxqBz0P3&FXChN}FPxoVDV4f4hS52U&#EkD2olVg8Tw|H783z&Jfh9z1JkO z^sCm}Pb7{nFksrF|!F6o*9B2fkLadDCu0aZ-9v=Vl0Vxh*+?q1e6e4^YEV zeMZtwZWRFxT4SRjCNWA!`NvVDpuBkT*%uC|$%U9fNWtH+t;BiG-o;>;XkYPBfP{oL$gp#+TXs&jN zmYEn3fa{x7gd<%2jlmqcUi=`Z8(+-CxubOr#r#mt z?$ybHvwS`B;s%u#9L!3Q!Jm^U#P80r7}q2Pf3qvYy! z0N(0u>K#^Yn2 zV$1Or3R|OX&8_S%Ih@GccW`>qGCfI@21^tMOY8>Q?oK^ra!cHQfj9Iy$zw6gjX~GF zH(PG=fnR=wFGF620Z0QtO`oYmj~5Q1x4T!`x&EREBduX4+!qBQ{}+bGdx_Dvv-M3!9MU-u&ka*q127 zcRBvo)W}632nARl(TMj#@c5x8w3GPj-{H+2itWodt?fG%#`h&~{LdW|%-71uuh(Y4 z_jUN28jAWM!3&B|!8kDIg-Pg(U{@WzznprP^;T#q!Krp1iNjwC$&C=o7L`LCSJftJ z9J7&x=%CcXG|Tjjg<7NHMWLE=cr(>&g2bxNUPtFEkp?Fd^;v|`J4%2$mu%QcsVARVMIRj{dG!*0<^ zqfo(~yJRX`OXFZ`)=TPzE4o?l%zsV(JaVZ%B?E9=zld8u&+6r3AxfmUT{nB6r9xBc_S_!?*L9E8ZFL=Xpgcsn>6S!41uXu@3OI0|&Z=1BJyfQq-tatB9oY+( zHS~Hip%*+HN3vC8xAY7D;3f1bvO11i>+|C2{bh{~3)S$%>$|ELF!%}l8 zNqrhwPW?=1>Dy4~@~qp8PVB{yfX`dxmqBJ7d>z_Mm8P4Wp%9aF)5<)va2v@-v!G+B zk}m@qh=>gq?zHixd*XSF7aX}tq-|CyQ^3vivlgx&GJ|2p;>P(BSiz zL$dcAdg!K|{pGE)zoLuRe-%8}C$-gG>gf+?6Ot^qR7TF0Ee0oRN{H}1dmkZ|Jrj-b zHUr6tS`Dm`C81#qu7(NIE&1Ha@mt^oKe$8rR;cU8F&GwI`#ksb-%pry?{zxRzT|?qeCb}+`es0Wh(UQ@Mgs$ zI#hTqhwcK$aktJ0vt;Q@X_3G;RIc!+3;Qrhow&&}XWhHD@HbOr7I_CbiS_+rcM+4a zc!T2K6e){RZbjX;CQ6%e`J^=Aev-w90&Yer{YllGI_TgP+46&v_*tesbJ^0CmRP5-=`1T`H8fBcH)Z ze}|f&jOYMbb|Md|j0p#JtQe{-Wm*A_^rDe+{^~R>%nKx zGl^9MA_;G4T*J$1<^GEFEld089=svt%HLoUv6-qkMsW0jMWJ2aIXC}pQH&cW!d=Jq zQ4v4bboVdIPtr6xttX46AvqZ!wI4h98Z16FJNFjl)13{hA98T@B)_6I*NAUsvPo3> zTYK(Ssn=1>dz&8j zgStCAZ#B7tp|{^|cKsvz3p}Icw84EgR{DEhSN!g16$m1I@&k4X(&8ykyRA}tszj+^ z#i?V=TjCICg<+-5{G0|ji)LkEd`>QdSoPV__@!t|eOmk$(qHW|uez^g!Tk+=L?A~_ z<=_>pVUfpVrjdZqX-f1)iW?dH!!+xNAGrtuP&bH4Oar2NEui)FLQ^K8&4c~j853AA zE%4esSXl^4+`l3uIo$*U-QMb~{HAB9XG^NpoxuR+G>4cW$hRFpSSjd8@<&%b-2A9* z`?<(gUL!i6)*@SfaGn=KQVRd5H?5$+R%LA)h?lNV&osP@`2a$6TBi3Gn`K~QHjI#o zsPXyF>EK^btoKsB#{O+er@5yHV@1Gu!052tQFj!gkPFZ0uyn0?w%*nuGn?i?topi` z-r4}v(nF|&duc3wgR!+TL7GIgZ79}USFLuaUU|Q*r`7Ex4evzE3X(haH8 zvgu2w&L2Pk(P`1-f}I-y(K%mq=PUL&RTKM^@HYbgv(BbUCwMkhV;+Qqo%w-N*c}Jt z$T_*JvUww)v0TEhF%oG-sTXN=wa|cJV4CTRluHj@McfG4Az%*OLEO-DDvD0yGaQIW z=()d$&_!7__%&7J?E=}zrrvmH_m-`2oM;L;-lxASl>mw8msReWMBx!Nx-s;H{XZ*q&Q}oUQJkDA2X`z{sFYcY$d+)5? z-C@DT5&^63FSt#%p}B9s2YHRnB4%JrE55G_kx;yqMr{n2k!aqrY*ec~Ktmcx!FXt; zOjDE8!Ur;c(E(+usEmH-yqr>ZjScWZ>LK!5?fF37u-yhik}xw{Gjkpk7x1t%PEzF9 zi47AXJu;k-vCb}Lr(jX{;J)k;zSjmW%6_5XanCH~x46c>ji~>6pYZrNL@XH!U)8b4 zvz;=ob?!=&FrFOC6^Pfr5as^7 zK-S%53|#BDBhMNu89TukD2V?J@V9I#RNzBV)0yxO>0x9pQ(8)?Y>ojMN9Q#+6MN=%9fl1}J;Xh#Rar0r zL4i?#4`v5o@YNGq$(~d!t%cm+8$(Zy&v5Z*;Sy}W1nAYhSha@KsYqgzZ({V1vw+p9 zR+Tu`$>acy?i!HDTU*bL&JJ>zkdHqY?eP{ya%C9D`L9C0@#-%s)}#Gl0zA`f@d$sB zxws-GR&!3Nh`#|0gmJ4A9B~FZ&Mw}`o`HGThWoRpv`#8a%?N_Qo7p7Co56J=JiGpg zmsakU^ppp!8=YYDp+-yn&_22!E&beKYZAYPvNK&f4zh&g?8~Fb9|7qydn@RlEHQX>PBp!Rr1I_7*q-(%_yZl=Zs^NUg&X2=*Z;VDzH{tUE zgZztTL4Q4=3aF2e1r(aQh^0?$v%_GPR4=_Jum#c@dKdKu!jS;s_Crbir6n;0 zX9!44Y^ccn)yH_Zn3e%Tl>3M1in1?Z!lP&_+9uj6E4T}(T;~y#O+|-IzT)v`nqj8| z&{Nrz6_t6M+t}J^x$AGn8;cCBe>f|$Hsk8GIKMB(@bR?S9Z7^_p>@{w6W5_6B!T<<{q_ zP{PP5WaMeXl~tbtgNJ`K&}p8_XM2M?yi{Y2z)jrX`zuKl* zJ(t52KLej^417peR-mjM%S^$g&OFjnoDD9~{RLi$_lh7HxrAa+BfNnxW2U>YC(2W; zn6Cr%=7U%&9vSFWBaH2Vm(&o2wK`)2uKD43_zu(D5Y0B4wP3`_DXns2!d@U$2Bw(1o-UVZPQ5XN6()aJ zE9M_cJMBR$?|#Tajaz0)EdPYu`F|TYw-V4sLz!6q&_?OE9MDGNJkYxXTon8zdwSmL zgPkli`+V^Iu{QvyoRpd?>KDO4Vaa1K;htKZeH4lh>A}S83#ymuutJ&_p1|Tg{=n)z zEpPe3!xvzC$Zpfu?oY)mn`OjV6VD+MpRJa}1xH?&WlM~*&^HV5=C}ESPut>MK%NZW267xul*bNf}yg3C7Tz9B9gP`1-i&geShMWN%K|eR zsnXi5%5~2#=;m0(lZ97yj%2Gx+f|UyTn++hb5$6!sA`H8%)XBcO__2~C<`Ms=8Of! zZ>3!=^j(d<4#iFvg?-54ju(eCzSSHn4{P8#u9x;*7s4upvlUa;`T$XX6?06O$!Mj5 z-gJdSPOr@sPozCaIzQJ_o0^jY)H7igKCu5IjQW{o_gVX)g&gk^3v722r1H$~W0_%kB@lj2_@Os zP)xoAv6Rj}R>M%(i9f{+R;rh^ML)6d!lDCh3aQ%e1*N|*Zr?eVw}gT%45oonEc~^A z=}LL)yU0%+6;lW;OBuSxpCMz24U{E_HGQ*o+Wu=mnE5->%jYtK&CFJB2s+)Z?Nj(R zsG(rCE=6O^Tp#zoqEm}13hlD8h_tX!clr68x)Nt+i1q-gFTU(_!igfrP~TN4p$+>9 z$21>X0;Ro<4B_N6xabFZ^L=1CU0*d5p97Y zc*a6KrBpbYluB(S3LFsnj*_iW&6OJQ2ZP^XKE(nLg1UnA4CF~dsD3c@kreEo2(VxA za4I5XoI(Iz_2%>?#_MvTpN{w;uH2!Im9mzN2WaL>%oGtDq?v}}CY>Y^`_~l4C8?6> zZ5`k{MqKqz!e1o++sub~%cJ#mSCsO%P`vO_V95?r$AMW_c;OTZ4%lnG`}LRxmo^&bsuxP|k2_lMlPgyDbxCJC$ z-I+Gfg4tGZ*KKrK(tsJ!f$08u%N2hWVH_**d`Qr1tAeRR0`(TZenNsy|9YiP_KeRk zUi18p7Uo|%PS}F9_$MCE*LO9=G@`Xe~tsJWRb+cJ*)bSN|v0LolgBWC@qI0_}OyT|j z<+CPo{?kdO=xV&HxF?Ng6C=2e)3-?`Wmi|T*Tqd@5qIBazbQ3X#GKS43_(z?OW?Dv zOUUtfQe%80SQqZ!n*&ss@!=8D4ADk~WT#wa#KJi0kG|qS22(;_}JF z9no6`vzo+oQpQYZg_L!pJCGO>b2&tv$#uy80Ak^Uak*b%v>gEqq&>a!uWobV2bUj|NLgOu;t}_dHR8QBXXKKv ze#>ah4-@5KT2e%gc(Hhi$XoW1Xgj$OKl?_dYGLd;U3u$$5JgXq=ui|mIe1nwW*JZ> zJareDqLJA?pQlQoZd3vP_uK}%PxqH$f`JJ$fPww^e*_x#|6fA+tFNpGQMH05Z`UA5 z5if$EM6rhwpvpwy&=J7_sE`^yD5l+BIu6*>}G|Tx{9oDBwV&z{$RwZNKYotJgxe`CgxsSXdPFMftB8rBmkw zcHnRs9-~47J6X%(kqn!vA!H!!o(g=TC)%%%s=^O`$&)czwz>I39_m>rA*G|kkG5DU znbgKxb0MTt8d0O7TXhngxAQu%MEnf~3$ zEHTZW$QtgSrgl#yR;I&iz1s+Sj&9M~Xv(?8H0hBM+WLbuC0A)chWolCg?|ru@z(Y# zDL^VYjqm3kf(rY~Sb}22T(9Tms~>G4Ty*+3l^R0<&|ELtnVFI{Cp23}l^$Dl&cKOz zt9xvlp}?B-7YBn-o#J&QF7wTkhc)v4@l)Aw6k48s#w%uDXngs zT)-dlwW%#`Z#$E;N#}@8?~l;7V<%k3&l=-F(_~bbn`UIlSqI_%l;n&I2mTYUgsx?? zX|hh+(IinE5z~9LC~oTS>NiXr*RoZaO{xDKEjDU`6O`{|LXFRg!;)`!OW_;PO(})# z_t%%w%coAn3SS>9=I=`Mgypt&t%)i>YVDt)3l1{!n@N$*b;6L-G45;ocl@RI3nT-! z$MWK?N%mcpP~F~0F#<6K08orgtobaYx?@?aS+zO3t3=SPz~-;Ye+(08Z3oUlapIkq zY=(Wpl4NCe$-|CTBqdiyb-8XfkFApu%>*AGdnMCSp4OixqV^4$ZC0=(o$669JRfV_ z$7VtrVWqUy)}f#D_s^Rq3g?o}iI%RR-Eci-okF-_5FUgQ{n@NV4N&c&E9a4u5_!K7 zy}-V0%}N3l-OS;>%dn7H)Y9)<))-A#AK!NAu%gZ$v+}g!xhk%MT>f^Y9nKQZ^43w2 zofH0dD<`8!n}cKIGl!bl)L2CalswtHO#6r~MM48h`x^sYJ2rw9JWy$W8nY+YW<+xv zj-$gW$4P-6Mmv8?3V8g5&lf@gSdz((2E3nI{4}X1ZsZbW=m_0LB88knX?`^m)Yrvo zsXOS@VM>%R9!KjdE$^eiVi^=fz&?)1xx35@`HCS@aS3ZjZw`P% zv3|4^MbP7(Oc+O(>~oYD2J5SrXykf?u^Yqb00(G<&KXas1GmZcz|Z&M`(&_u;d6h7 z<&@-PGdI0Hklp^ZLMkF}$i;F9DxrbVuO~=W=4ZT>0(&}!k!Xd=AzMD=EP06F=vg(k zTIyOymCLeu(bZ#$#Y3BAXMpg+%|`Lp!gXs$OU(n3qrq?HIp-}keL=C7KF1_z zoF^G1J^gAg0kXz0#ET=u709qIz^MArqc4`gNnwezkl}^F8-b@r9JCix&oQ675AKJxxuJ~#S3@1MTHX^?}rG;^73+9AE~zgJP_yhUL^RgOE0CW(-kuVP7* ze!%fO0R$yvIje$B0F5bT`|ZV6cXur>X{Fl!b(5U64x00f|12`0$K%3gqVSWYsvfunikG0>i)D9<9cVq2D`hk9 zSJqy#YBY7+<7IJ{DQF$2et++3y~2KorF-2o0>c~AGfArbiHsWWk^BX0CzwRu5luWx zr?~EBl}Xja!u)6NM=7ZN)xTJFL%QbkX5mbogBtwlb}Q~3`wfoyUKGuVPvOP)d)0S_ zg;ZW0`=yTkd>YxGtNn#;RA0e;Q*D-IGO7k=L`k_Rgaj$pP?rw}t!EHRv^m<9*{dWr zfg+ZB&hav=x!85m1#Kd1*!JSYD1RNe(}u4G@oajYY^qp%!}Qu;N^iG1qUN1wDL zdwyApe08XkeTQp5vE%$X0P2BBy_kX0$C0l;mSf25=na<$NR&YIx!NK6K@^ zgpLcVKXB!zW-rRm04sXg0=RbWy7>0LfqQ=<0I!Q5)yp|cyB0$nY%lDtk4h!tDcj`gOX&}!2$AQXgEr1@!KjsVid1yar0^`*&X`qKWI z>s98C0#G#VNGCrsuB!*CO^gNjtW@0U(S8?v7u}OksGU42(aB(7W{jin!_a9f|M_{T z8t%|kUfF`gITqJaRF)@1^U*PNbIUg2*8I{&Ez6(&Jp)vEX{7y*|8BS!0=^Vx*|pb- zr0*U-tAFAAN`&~`{H1a(@YOj*5{2_UOj0qk*(j~{N+#p+jYX1pei5wESJRoCjmPCC z8~5G(a)6O8SfQl;mDc#*UHtjrFNUf7Drls1o{Ll!7382wW%GP4Np@)(_2y*XQ*9nH z{UNM=QndOL{_a&20pDT52Kv5IlqOl=U#puxr8qjYqS>|o`l<3DS8kxJL(`W!nCB>> ze9w7q>2wu|DSzdM#M*+QGB!GN3o%|BwwHW4=RVe~|L$MTmzF1Js#hp%^XSW{{!t`n zRB8V{6|Un{OVsWm!}L#HRK-5vB+Puh1dGx$Z%9@Toei}(QF9-vk3_S;>5K4&+l5z0~(A1^;zu zn}fJOs3vKR|75tJ1_t(U7LvBV0uoG#KE3kzh4?n;JIJ)U;q`;StF$b2!Wbi^;22miL>@D6fkb2P^ZUccgfY1A`f^RwhOo zYdSV}C*JgV%pTGIcG5-prpU^OageuX?9x}59p@DWnIlbJfISjBLyLl}=1>KCP6#^G z9V+o$7e6L5E*dSK%2$a1vej3`S5WneT` z%h!g?;L`%NfOaW9S{2u%Z2EDbvr}A=ryo=toUw_T-=bIcX{~ z=#v&F;=W{tr)}>dSjkVu8Cf=uD-SwvZjkTaVV35UN-U{#MT|2-+8;krpwIYu3$yye zJL%szk0%143*3(GhyHdhOK1XF3_=2Nt(nSiN%jW3(`5VD1HD)odk zkhc_wQ+5=H*U(?c9J!jf!y1I6C0h!;%82}`stSc^6lW{zxdq1$i!8Rd4(bhco#J0Y zqWfp+Z#=LmF?<0Jxf4{`RaKVi%4a=Nn&#z10_3R_9T#@L zWh|*Z$Co}Tetigd1MhmV;rv9^bTx4xy(%LSXn9kt?E2LjmL z3OHR1pPJ4jW+S*tURMrGG2&}z{gx@MHE`P&i(C-vXH{z8yMV!0L%(%j$m+hV2A7|fA0&|ozh$gO! zqOS>TgoW`~`$7|Hk*HbOt37iQy}X1-lzFL*W)50rTH+!~9KzNV*t2rL<5Bg!DdQ^{ z*u#gI)x#1hv9pGYQmGZ~Cdw4jz*aPQfw3D^L}sVpL795`A6W2-aM2VG9E_o9D>5R?OVnGF~DDgtr@^SdO=F%SvaCSsrMfeXvS~53y&48wgz6 zu!0mv=P=n?#cr5WYG;Ar#Kz%IXz}kM5eFj0cm7e7bn0I;NSDW{$baQc>j((Ef#SEK zT|;UHu0fP+|E-~{V%|+?tK6{uTvk@USKk`WV3MY91yvvPt2IEXr$`Ls47ds@_@RrJ z2Sk~hzV&4wnm$Z2CywOeB=h3B;ERi64zVMkLQ|1)YLW7Ckur`}wK@QnVeH#($5xjE zZzmu!72Jb}!@!k7HhY*aDk7O1fx4C%-H|L*k_7S%Vwmb@dsSlW0Lu%D45^|}hYXyi zaws{8Nep#E?JXI$sVxe0J2tOH`T=K6hdKLE)uyV7x%gln4v&JA9A2jZ2KY>$>(cI! z1D}Prmp+>+EZvu15Cqpn;8Fkfqnf}>?ODkS!oR$u+c>uiW_A-KDg1z-n6>@1j0mQ`wA*5m2DSV6uU{_%t|Sz%Vg*@ zrDB)pX(Mf0rANu3%sqCU_`3CV7v$QA4&-0t>r_ai<~ODOJ_vFR!nRPk;$$+tm_6uc z+(G*49KQ4tUgxtRR~CeX0~Vs#GY$T8jmmPpLmVjSd9AN9V0l;^n0g;y<)Wi^oBo(Y#mVlCC?7JpF(k4VyKJ0IOFfs$Ed3}go z{ovEuGn=3%ydG>A5a6UYy#V$RAwxmtp=Td43V&F$U^pn2%*qtN2E|zLOYlS`F1Q#!y4a$nqgI210_ z1*q?eAZbYO_`-5nZi^B>6aOi2_dnG>=!aS~itsT4-!f{CV;MS0QWq;1dC0(O{n?V3 zR3;SzrE}kyaRh95H-Ve<{TEZmoEy<#Q*O7oW}4@T0nZ?eotO_0_e{}KoH96axmrIF zh3ki2(h>Nn(01_7q5b8NaMAN_~Tv*y0J zJI$C`nNP#~bv=UB1H!HWilLZq2#KGV@-0V3Qj-|p5`Rad~&D<7n5>d-PCWqTeCP-qo>lqJ(%JeJ8v$J2I3pg+Y1|=-^IN4(VC8 z+X|3ZXSPVxiI>;?x||NFB888bw*oCVvIP-hJ3;O+$nNK#V6UDJn=W7WN(4>##$<1> zmb>Mo4?w1)i)WKO6l2i$0^ou4{&9TofyO&PlBH-`csJoo|BdjY$A$o5+_4AoGhJHL z^pc}#nw&TN3%Q@Tj#%j%%8TixbIET_41G7Dt=Y?1>K(Kx^4%c}q}Z|N6@s{jls@Rt zFHXMaK~gyr8(Al%)u3LAZ>uri)3;=#Dh=O@GnzZ zB^9)slZA1n@h!fW4)nCF-+__1BzZ0{J4&7XD$CXy=1sv4U}h7PQ`7e&;#nt=>1U@R;a<0hM~OFBeE~z6e8|Su z0>`5AvgC34cYF;J9L^trM>!Z&95c4siDZGXxI-;IEq+iy_{Vq@;dVw4wY_iteR&Yg zE#CT#(yA}p0|759+ej7vUw`@rBK3!Y5Kv`Pc32oyAh#^O{to-b3!20h3v!f8A_-fB znwC1G-(j=dF5JDbhT^7-oX7)uy@TBnRT|G(&o zgQdYtuePzXT}!(D6y>mU_n?!{kHbCT2-8X}TA9(Lo%Ce+C)^CTPlZqG&%8mJF(Ahv zvuZ{%x9zTa81G?v5^Eq&!~Ja@U9}6-Ik{HLD6^$4g>I#JdHw{q=`C`pbd~9Z9)k$O z=CSrlXwN~rGL%;gSFR{D6@T|exslpQ-s0BVvtH|Osm3C-;N`CR zni7PMehR&uQ_@fg6i8=*vi%-yQy}$+5ix*;R+M-p^iWnz9Rr51(#Ykw(MG#mNq%u& z_qH7-#-AYyge3XaVh#&1P>xgxB>zUt*gYGVa`H45!mqKiM%Iz(5NWeRBpX4s(ClF! zG7E}+@V5Nfw@|V+?(L)?Z1_j@l}a0>aCn)rdPcZ$bAe!j`HpV=OR@huoYvlCX^kc> zU?zv}!*5u!2H^Bm z&J#A5>Bs?7$jwSyV~rkYF>v|~FqT{rFA&dRX(jixk+WGAea>jGITzLHiN!9%>@1t^ z{8C`}wZq4jVNZ(lQuKW7*YjTyBGc>i^Zklz7s46-JH=UOm5&)-VMs$iRhsrr`9uWA z$$W(x4BAe7S$A>NFiHkh{ha#$La5I}cwR{Q*nwZfz?n$Ag@nvb32J^kE3u>Sd>$I2X)JjV! zg+D7W%JOsfwXF`U>9wW}PwBC*K9MM$!6%NhEF)f&r4)!k$!)73lXkF@?z7uOw5$M&^3h|bYDjzsSyY6M3_joyhGy+l8V5F9O{ zw-LSf-dmKYQKKF;dWlX*2od5t*K_av-F!2D%vx)|YwvmXJoC)jd)9i%wKf_$XsTz0 z%whZ%$sZGJI6Zo_g@v^RgsSHDw+GAk?NTjwBps^k8fUb$58F?kMKpAKFQyGHa2ZVS zQOv-^E-Q6oI+#|WbyrU0=sacy5OVM+N1|w%3w;k(;90LK^1%Qh(lG1_yTWG|5#PvQ z%KKyVpq==!p{0rYr%Bn525e#GI|}Cw)sx>`^z<9J3yKRHa$n4YLSQUPBVvWt&IPrt zH>}sDPQzP!4z1!GlJxg}dFH4(Z%q?EmS7kbdO}I*$b#T0bGUdjX;Esm(IVPzSJv_+ z1eV=8LCP*F6=J@l7f97ZvO;FufY2JoPw|!NTr(C{I?G+2U_B<}#2|T0PET^g%rc@f zFz_4wg;6Nlb}|t!BsxXU3kXN3=|_FXr6GIuw2vm8;)IFD?&?_|@Jg|dbIVFRoO|hT zX@K7^P|tExBinHKllkO?BGz=miPp?d8b8%13WFC|RjkKKG#%!LJb&-u3=s8LCz`KkBbv%Dgqps89@S z@}1o@Ce%R#9dw7b6+ha^-pXS(OIj;Li&Msx-V?TGct^?~1@9WGpUFFtNNq6&?I-{3!@#rnwF zY-!6PlJpCsY1l3n@k~nR9kUz@$pOKYI>T7F*6JUmB6w7}HCNG$*xF{*~+u zu*7Ypoybre3SrisSQ2*4Up&hV8BggH?$!CtWhC7%oIec_wPVqnpx7-mqnDJXfC-&u z;vVhXoy()0&)ZiV?8_M!CaNUD#Fi2|)!ADnV3e^SxKcmLLAxgxm>b*6+GjMz>z%%z zxs*R=L?0u1%#FDAOjY+@rI*$e%iuva?Fq_Sho9cd!@j-kxSN1+#t6IkP&; z+@sZ^<&RpT0=Q|Q=_>IJtxbIO5PHoXA?Pm5kGRSjfxhLi=nD1MBJhN^a^@GN=9z2$V z<4CD8i#cB1Io5w(02ulBhT&+mp6BEwktl1E9!l#h>bZW!cMEUMgvo`aF>vX-$E^^z zvg~J8815r_SY}h7LXu8Uk@DehY~xe$4eU_ob1%`s8f}ZstEtx|7*q!(W-P@CuT89fTx)CHKaTcPd zb0@oPG7q55?u?{WjIQ`=7yGtl%!~@2)5A?n{4Zr`H-z#z(_tOujQ6tPaQpqu(FPHZ zqOc1x%Too$6Y_!=g#BT<8-7Lmy-4-_Fh<^`>(d*Grl%3F#(A_ZJ13*O(dce4>YQ|| znDCe>tY0gkh-5l&0X2IHKr)^bQ1xa)aKNg0)YZXXLn(52>aj?w{iWVTkmEg3I9_Qq z-j|wZS&;R?%IenZlnGKazbZOOiF6%x3NSZpq$a&dAO4i?{Na(9z-zzXzrRs*((5t{ zGEF{})|SF&BsHf#HODy@33+scKT?bt%@>Ug-5_mCPM}|7=x2)NxD)eJkq0xE0I{U7 zG$0EPNgv^gQ#OfWKCR%Ha>y~> zr^3V2j}n0sXm{s`w5M1MdZv3IrS>YlzYs1M^y#>fulboWJlzcvl@2;g=6?lo_d*jU-=E(g)e!NSO*M z>WgYio1M;EwOEw?#L;y9iG#D8@~=)z53E1CHMQ|YH=^! z>Z|hRsR(?7xv25J<{iNfjcto+^mpdC_vWE(4tMF8_vz{8H%KedX2KDdM1$0oz(d+I zx_0_SK{d?BooBd5$2L=~$Ap;$zrWgwp*<&#D`Xh>G12UaW_OLYe5Rh<_~Ey-EHYD8 zcifiD)PbbJ0r!ym4Vqz1F{UG%yf&)~{*ug-awp`FEc#oLPP*=020M(o`a1xIb{s^60F_;+;0W^?VNXrTfv{py_}1)nfC`?NVbrFfGtTB^l6>2QEzy11qw znj8566w_&#K$A?)KmI#tjqVjW^^d1c=Ci7s4>H!q-XF}@{W>gym0f?&dhUnu;O$#} zRf`i$LM8r?>VY_b!AxI{GO4FIunc-Hd<3t*RK1l|8qwzwP0O&j+03#bED_J=?-AV= z$u2B{2lb@6%y5qM_6afLcAkHy{86{5%v-Juk|I>5t2J`iX13?4(^|RkXwpPjx#xYi zi`(S$YY#%bwx!&pw9l5YGv$sMYYAWn!53CbABqyon8UVsR4SZG8ySA6&zZud+~{ZLBPhMNE*QQuvAfkXTy z!SLoqu-Ulb>km8Q42FilPx-y37loy%@02HM2(|^4w9zKcYzg7#e7nzSi6yD?wSb{>>LF?IK}A0E@+e zulMRg`xq@tfcvL+i}O+P3|XC$btdfKY1gAjT^|F@i-kHW=Y2ogfw~uSc-B};vuU8mE3AUy><{b>#jXdR3 zL^Q5d7AM9>u3@A*8(iZUqY7s<<8RQh z>M-AQfCzmKG)Ko#8H21OXlO8C!j~C1eG5g5JlpjoLlM)o3y)YdY+%LAg;cjHK7@tyovN)WXVJKRBD!&;}A|Dli9Fhy6VpE@V z;oLPJ_<^?=_}0ryraRB)n)>-;lK{4A<8DCtG9ehX6lThPCS7Tk(q8G9tbjX4VtI&( zUwO?r;v1X_(2#>U7O5c_lps^{i`J4V(}C3PPIGc{sg6g^9aZbs9tv9{K}PPn`w z-D@U*LCXy(%x5D0#k=4pWAc-wlBp+couOTF$O5ZNwqJ+|*HH;#Jvt@j zgwPk1L&WuDCgUSJY_}__#kZ`HPd2ucm#ebiQgC7QD;hN%n*gqJ20=pjd@ESJZoaMK zk+ZUl4JOVzu_1$6cJYi1v%W5eq=X3PTK&}dQb(378+yXC^jY0>$sziUbt~*rH%Xi1 znZaX=lscg0bnV2Lx7!M8en;3ZMj}GHi_Sm`5zdbjw6RUjg>|$E>-6i!A z9l251yS4-JW&8%;3ekhbis{sSLMn{5TO|c8Oo&P zpCPkB#k|CoX`1d;m&TeEhU-%$xJsVdNVtyPLT*`ViFJHaih&ld*R0cGdA~wk(g|K! zlTugN98Y!alJ;2_gQsDlGTj8!W1ul4DmYX9p?)Le@tYlM+$xT_APp?z9qno=d-Aqu zA<|`VbAEACD`9_*(YNn!5XwXbU2TCbGhX*x4{JtcG_Zc16b3huw?%o9w?!=B5v{_o zzPd4gZb5R)WXDM75N%H85;}NY@ zcNW;pkzpAW>5l-RTjc&iBgH&8f}{C`STBlZON$A&OUsedjw1~Y2*|}pe1mK!NX5uk z=#+~cp;kGz&|XIpRWkE_Yat~$VG;<#+$4vi_mfsjiaWNre#X>ywhjWEau!mpVgYko zO6`eITeX7nxPyf3I8K^T&dL>J;XCJ|&P%)Vp8|I66b9mzVx#L;T#1^*N6EX&96N(0 za$l@)V2uNIDdO*25&jzyW2J3ozNK*r#5DcQF;<%Fmx1Tcb$q3N<5#oNmMOcXfa^lz zM%MNedQ%oz?@an{9ls8gNnL*V(pTEgpc+XQwwU*Xp{C4{3syh6Dwrk^_PS~mvyK%# zw08!(V$Gp|=aKOo|L?sZ#SRh3PQNlu>8sv}lJKJRCS>;amxxr4WmPw@wwgO{7X=Pd zaRZ>&Be7(=zTn7f7v_x4W%ed0xRxgo4Xm|2!0DdoV~WjHkq3v3vYGxgi;<_Tz-K@= zzdzJ_S37)`PpvHgQbSA?di{)Xxpz9au6sMu-i2p17d-@uv4gFVC@gkC`9We&3V8?Z7_wX2T{8HateSGg~yjj4hY!@muh=srF zgCUCH`0&+d45g=;f+2PCd|50pfZKy`{eJbOF%yaHaZ3b?b=}W(H|+>%^^a7UrXOv{ zaZC=^@ZR~Ky5r_IOpZQ{ll;s!KNn1+8VHYML}%(E#>z6W-CO{7sa8A7Ydn0Svl6#W z>CPO1kNBc5r)l5fQ;T()3F+}#Hk00w?8Dl&39PMlQSTev-*JMnwJIEliyE;X!zj;s zQ~O*dx)6w)Z}Z%Di_(YIGSa%r4$a=+G#ssVASP?Xx2JS#q3a@B;m%;P6)kNHsV`j+PYEr#^(FGL)xQbWC6qr)fo1! zTCrI8;OLA^w%HLj4c@iH4h=Wba8HpAQf=F&J8QE$h=RK82gGZ3la&T(ae z93^yxzgR9(l1D{;$hNgC#}Ak5J2aU%F1hC!8%bJzez6?JI!0*LyP6$$q&M#88hTUe zGOoJP{^t`BvWF7^I+I@)diHK6xX~zV0SaxGMXbh`s+H< zuPF{Xo($G4GfC&SLW1>JYqC$Z1IWXXL2Tbt4 z#weWIMidDgz&A(*{evFv9~A1EAK-%f5AeTiaX`rs7e(>+{!qWlH|-g$>AjdI0j*xmBYS32vwCe?MH%&_w?5<9>Dv zlo%ldrY_lA3OekfQb$M2E#z%X18LNE*(%c!U;;XzEzJ$WI*ri{uZ$T%~281 zF&sFyVF7T)puor|5lGz)P`Bkmshpq`H$ZSq3^d>dxQ-cvv|gevG=PIWGe9`b3Br60 zOuU5x^e$9@14>Qxxm6EriBYX!067jpzeE+4(gFAr!XU|DR3j5$JwXNi-)0ILn%E!r z&h_RNr0NX;wgLlghQuRG!vwBt{E>sK=g0a3@^nvN%h zpnrspfA|f?15c24pwpCF>=;rO^gSJTlF9)*o{|JzWB~zF79gCwTMO~D7znEXB?{dR z5jPD1p%ko3jE{PXGsf41YZ< hFv}l(fhGZoel|*VB`j2!jD{wRI`>e6%1{2K{U6dpN*w?I delta 20005 zcmV)NK)1ig$^*c%1F$Or3aZ&=*aHOs0O|>o?>!llP5~5?9@GYZjaFM%6IT@ej+ta& z90g-QgNlPU5-y3g)>g2zO1&TfEdgvq+YZSgj810K$;3T}Lh^P8=451Lj40^Byo?0}XR#>b#!kfWj*MIfZVGox zV!0)j+Z}jU!FzaLhTef?vCS(ugn|st5IJX9hC9I!N+cHty)Km8Rina?%-BvbU3Bz<$Y0>ZqLf+3lDh1@ zi(FJZz(dMg@JxtXs8aDEK4R$J6kl7uL$s^-7@ttZ0`!xnUEzX96`$g0kZ+w9>Uq;x z7P)<<;&XhV;!Au*X#J!{gQP}NL$`<$%Kwpyukj7ldo%1@)pCsz-zX5n#Ywwr7BtIt zHIpiT?{dvu<(dyn3w&x<&(CRw6^IK4)xcP;3J==g@ycLI#kcrQr1m|-;Qzc`4Ewk1 zL%KnmM-9n#EwwgE*tHktrij`^vhjLMjW-v2s;-&YqM0Gho|bY2?Gh_;H~X;S@>26f z3_P@2c(<6l*L8%L=5YmeV4lWY@;v#UNrfti;`PK zRMfn{r_Cz;Rk5o^U@- z(5m_h7({}e)U6mIEiz^Uq$iV%4~?v0$Lte?a?&4=a-q>0!Zk#)>yT^cSVQNSv<@XM z)vz-zMb#R1jfLak=x);P%7voc*&6nLj78!RMuKQAG)(V%Z^Wg)5PK}lenSs~NKW#S zJAqDG`zZJU!f_D8^wB?!eq6#~EI`9;!djpck^B`u!FuvyH;fSv5XUG|1SCSg8`883 zk;Mg^#7h+AG_9xbGJzI8PvaHRI#Z{@KYNwVUL#3A*mDXd%NUT+Eu+`_56S3%lIiyg zFy>{=Fiw%^n^R}~XUZx<&*>-V%?(HQtzmx+@tKjQ6QMIwk96oq93JVBP6?7~=!+hx z;oxIL;^AK&N$jWR|2)B=T(m#nY8{8yp#ABUR?yQ+sR@!a0zFEwPtyJj!4`CAq@$r5 z69iajO>Yo0?a{$JP`eR&hM0^EHyAtcFX_=W_B!MIf0K;}@dd}_?x`D-8xBH$PZL2D zJ+m!rUA9yn2;J0lWIs%62sHbPTDogPBWca`j1S|L|=qx;t%jg z8Sj*W4KzjfVQ1#vbIv_?ZsynT?>_hmdy5+gJs-y zkbrMv#l{_m@n>Ni>gNmzKflF)kSxoZV7OQbWAVDZyCc*az7tWztH>&kwzvw-xgSjG zM%bds_d zvjQcCsk+b`MDIvd8_0z+W?1y|mG}Gu4`QK%;h>U@y9^8d$ik~7)3vpKS7eww2gu-T z%C@SC_0aU5K28;k4;N`nlEyin7$zH9Hw#VE@7tD8HtxA7AfQY9n>gk&z$A+{R$ZFz z15@OojYkZH|GP|v?1`~ciJ6g2Gh}+ih{yF{v)j^Qmtn%pMM*;HF2k~48GvXN#`RME zY>45>5a2&jGpA!@Ld$Z0gR3>AIGITL`Ry`8Zb*skvYGJoh&C}#uf~P>60po5K`($# z0j)FxjIA8N`brxM8Tya+f*)}SW@3IG5I2mk;8K>#(jJe$A{005jF001EXlcCfdlaJK~ zf1Ozgd|by_|9{f%zNgjG;q|$`vQF$+)@eJA9m|OmOTJ{wlB|{F%68&BNl((+t6k;o zTiZ%XLrM*$C4{3i&C#Sl+dwJcwDro3+9m|*K!I{opyen~&QR_aXj=C_vxj!2tw`%% zG;ijcZ|1xIGqd^Jw_f@TfSvNzAlBp8e}d@2XRFw|p_NlOUGkPlE{I&w_X!UsTgyQq7;6 z_=_OkkH1vSUm5ta`u=qg&*5)^_*;BMHGfw{X@76xAA!v-(9$sW7F|5ML1c@mW*+{7QfQ#Qg6yK z15X$d3d(X>VaiIi>ncN58?wfff3PWQ4OwT(`XGj6gDD$Lxkc?8p(e7)lv_=?&6Lfi zY%%3_Q?{DYpf=cMNTVT50;?;LaNN$gok}?=L8#A7UY^a`k zd#dN$(4qclS8os5y3gAe?Y6j`m}rZ7ZY(jePf*jDOr$(J;SJgGv|~!Mf1tLnzxPQ0 zp=k76=TUAVkgiJQYe99#;NioE`p-qXP9LfS8b}JnlM@pT<*n;Zx)W^^u00la+Ag{F z^t9u)b?ZrrF*xqAryTm1y&=a<#gYj@{j{5$aGg}DJC^dCgxaU2+&%}BmlE-$J=V8? zojV8ajwNE=enCgW5*jQve|<4!+mOK5nH-~%b=|Rq)03VWaohoWBtx@5)JuCEE_i;*OSJ*kfZ#HKt1`E3;(GNqMnEe@<3y=~^bhq06Jr zw3_7N`n=4pgy*;kJ5J@&ZhXP6-CS0iPC4#@2`87S4E#uXd|YKr#hDK3lSohXJ4*K& z+D>nI-A-b{n`A8WIo6p>DwUQnM`|vRRwc; z)82I2qthLGiqjP_e=c8HnC(i;Pa4uo+PG>*ePfCu0x4YT>-Z@l*z1e z08&5Uc-ckn3CEjE(wA$C_*`c^PHAn~Ir3YMX3p~(*`Zqse^0$5=ebBlUD59Bbr0EY zJf^r-7I764DbKj4h%ule%g*Ye6&fdD3d%G zO{U#ZN0k_Je>OF3u)C{#3c*gkH_e~Nza>ZomOC>G&kf< zOLpTUg4QMAY4hT9hjL_(A$M7_SK2MvCwE(NkLKcCP)&y=opR8^hwxzwFJX=@P>Q!`f1g`&NDfjf8bZqOIb256M`$J4)phQ^&E)|rkH4vqXPqd5sey=QrL(jFFJ0-PEgyFGs>eP zGLH-qFB!=rbA*c`N3;VYV?2o5*hpIOv_|^k4lzS5OT}1Gk#s>|w3S(?#3kL>!#R*z zy|4y4(y_R%&_Gr_<()|jKaY=C5>r;5mkXA}e}(x_uhzCwY`nEY!;~cnVW|e^!G}P< zpw2CsmWOh=RJ?X`VMT2gdrI=A;=Kdl9aHD{euICTbS2rxmd!NU%I>uE(s!v zdb#!TRJ?U0mKbY2XnVFdGwl$R>3w|~Et}>BURJdZ9-HnA5p;gDejZw}DW_=9`}4V` zf4p5LFsaC;m^ZmZ;A5#sBI!j^>FMbtbr_3~HbeY~92+{J^Ys#uEL$?Ixsp+}#RI66 z*q6gS6}Zcm%&02VK-PLO2WwVtl!L3f>~LzHVkA?oSriSjS3RR%!JVGofQ{i0)3wN0fOCi_}e^%!9eBI^nhKOD6jHmhKkJVyKNEERb7jt(> zf(%T$$xGQw*Sg}0kIp1K`*KmJSC&1xO7m}qcSB06W+@PlX`MHt&#)!U)>nafdltMM zf+@#4=#1OxI1_(e(Q#P9r}wB)Vr`eitn2FYhu!>zFEDjsEas;4wevI!$xCW~e-t?9 z?|91^7GE^O4driKYOa>%CW-^GcEO${7q}3u>USPW^L9G#sI6u0Ipy!vwY0P(zN?E& zExzt$??j!Yw@}*N#apJUuc-cpGaYJJUy>5J+iTiY-pr3nFArI&dbY(i}uOWx4%3bo5&%fhye}&Oh!vsZcFJ9a^X}eM7+r+3-a$!24xmB)Ho2KvL ztwZhdClKE$UOGh)i3w%v@&)&^W5<-v{!4DmV*(oVZC96~RPt#``e;0vQr9NNBsx0j zD6BEqKblN=*o#yDrSmI*x0z<#Ij33XGac#NBh;mrRjHiBbSyj$L z^$u-ZI!6k~pM9n`bS@Puf0cdn&yv7+(w(xs1tyg7R2dU;T-b#5=z+k2fiPk?&;A7f z6^LUkrjRI%lN?VMjUPfty&l*PsRxAqrgL9DBlr!H_cCVKKFrY|{P6Kx)z~D>Ewhjp z^)`=a#tOEZVB%K1mA%F+BfbxB(?9D~X+ffUN>qjJDPfgb#G^S8fA8ds`XO**<18u~ zo35d?kjbYz4_#2zAA;1Y^UhYO34Q!^gE!^*R)M6`Epn;Cqh7Ht0>9Q-kV?mdV z1zk33Gb?n@)4Hgh(#l6FA5l52dbO6oija97RX0#Ohv2ZxqWU^4rAwvOrB<(Rp$}TI z9NV>QE4wZy`|X-nf0mQ@19%5TWW8Fc7uGdrP?JIJsm7+}S=7zjnBDgd?z@ZqJN3Si z?2>{_b-02b)UxXEL)wc!%)XD5DEsfq3#;6Ofd1L9h7Y55f75l;XRxe2Fo)3a9F`AL z@QPWi>-pYzq5kv6?Pl({6-)p>Wv9U~Sl!!Mb+;f3gOA%4|2)Xv6Mc)t>6A zJvCu}*vw$#@b0RL=P`91w`34`3M)T`O`%&exNQ!bheKOtar?`wYF1WVvG>%hs@C7? zRn;r7b*kz;&!MUD6Q~Sr%b@X;COUhnNeSFQNPU`C2CuBD`6QYGXbGE@E2}bSe&Oc3 z^_rFpTEqSue=x)T4BA?5pplgAFW|QJy7Kdenh)2#{Gv{}&*OEv>~(xqf3qQdFVhOx z%lUoexQFiF&jh)b)ceqk1K5cU&UCUph%OvPACA!BM=`|F7>=>}jx(LQnch7NOD~=v z$J028527C*CFjR6fLC#fvQOg+ID;?YEWV5f@D-e+e-@|lb<*CzSrI%Sew>pk*kWNs zr@)U=n_9ercjHGG)SY-1k27%%O1{FmCzvh|veti$e^r$FHvBkyLCSmtKY^b_HFdm< z_pnz(YhJ@o(N>>IjC@M5mrE)3vME&|)p!!`L#3#+&aUu_iKl3jUnlpgsJh9GYYeP6 zu*1MJe+Hg4@O}f&8F=16zkw4FALZO+jV{F{n(G_rxJgX|ix~+~H)&1D3=~}qeBdSv zu71%>{vR3G+@w8a_bn_NX`%8!#NSZn1k2-e~nGE*xS?c8hkH?+M6gVgMClK(n)+b zlejr_&m8s-&*I+DeHk2RBoue>n?Wb5a~_Yf*qEdq({$BCbYu#viE|O6-aW*)n09NCW-7+^XHcj4zWHojeBcEua0W{g%8jMz*jzVEY8DRBx7aOQEk<6s7dPBe!O ze`jzcbhPr*=*r+&Pjl$F8h86R9!U_`2DI5^imzdk zx6-V=#LJT`t9};LBunX07Sm%aB;~KOfAqi_a{L0zx02kqF=`*B8}^d=OZa6*aFRaG z(jH^fui{1a`Uw&rV^3lB;{{(ouKmg@2<3kqpP-J)!%e8TN%56BH(3hTR7yv0@?7y1 zNF-<~mt-)TJEWfGNCk68Xqbo8iO^}bJE{f6iVl zWXvjkQofe~e3H7qk7e`}VeXltOxaP;euvIwUSX*5b$y~+1d>k{GNl^wO*CtL`#Jd% z=5l&|kwR2r-XFT38g_>s(Au6;+J+uv+wKe5>f;ZMs81j?T5swAGyi?jVIM#K=rGeH zIvfbIXM_XMVY4YZTpws=W3)uCU1My%3bR%4JoWql)UTBxmUNi9M_7GZS$)d3qgjP= zwgm{tpVE=B7>G}6+d>3@&uH7ig!-5D4I#oRdWAhd_t}kKVJ|?=SGD9{#e}{_RbX8I zUriJ0|3ywB_-(VTAEFfsa6sYpV+Q~L2@sQSG#Zo8Kn9bb*9d=|SNVS&WgULr>@m~L zgrc` z%9W4Gm5-_TxK#N>4EN!aa^+La_%uEv1@4#A&o<*QKG%$Kd|ozRQ1L~%{G}MajIYFS zr*xLVS7q~ng0HFgx{3!?d_%=IW9Y=U)i}b>YKJ~9WG7_v<1#A z-Oi9-4>Zdp=pr)itsZh`v~O9?K#ghsQ%6WM)EKez*_1iYhTY8~jaC+?$Ct16Z zhYr&cqtu${tPgI?o6c85AIi!I3yxM+<@yioJDGnm^5w9^rgeA9a0Brc+c2_)KIepO zIeM0g7?dl?YwO@dVlz9{NaTkHvwBV@5_oST=0tY~3rmbhmf0 zKn;HY@;Xy=UBmWLy}VfIt^uCduv2t1MsQbJIUL&^k9dEo!F&e557-ZwXQV$0Q~}2*OTkkqG@FfSHlnSBMndC zG{f8NOlf#p&iCNQ8h(PGYIsIAKa*=e$FqM5&S-cJ&kIDl^SbM4_=Vg)i&=WD1e(S> zq{Whga~kGwUc(Expx~DpeuWn`yo8rE{2IT}@Ctsb;dj!)tGJuo=rb(Clj`Iduhel* z(a`Vl2L*rB@EZQ4;dT63!(Z@M3O67inbYeOt!#(wcpXLiUNhf8=5%-tJJBtm4jF%X z!LfU2^$mHVH}N+Of0zDmlXtXwsVt%G`j88(Su*C8NR%r9tKdS8GKc3E`aOenz;P=l z^ZnGE?3#;%Bb73)p?iK_32bjzxEhw6Md=<&$ePoVGrWVkJWD`Mh4Vpu+Ne*B`Qj>V z+f4DUM1v}}XsOISDyp6nED2nnXjFei>&s!YS?H^f!-vb75;Y3}&gI0pccS1}Mb9{> zdy~8vJ(DpCtos{S`O}wO(Hk6N{;pOvFg9Q86j|s-T$9x|vG76YtbYrmS;>229_>bn zws9CMXdAwjX(yNSuXRBf%JpffFvKrvjCX7~jLynNfgPQPyh%dddHIn0D>r|(<0APt zf1_%)I=rs#N*9Jk;!??8GWL+ePmMZaNy=1UZ~ot~>y#HiO{!T<-S$N7ekG+TqfF|B zLE|K|Gi>`^1;EV`K-c8}Ao_KenBPH0)V{VgZ~Q#}Dp`Q-?MJ}kBgT@KDgbsfBZic|kh_<%M2Nqzzt=#jO^?Saw ze$U6&@A(?@FF}aEJ=ja_TR9p>6BPD0CfCnGByXBUQ?hFop=3Nfi*Pa?nMEWSkIo{R zJO|}DN;aXFZIt@J2K2FQ=Nc_wA3gy1Bk75+n0%?YM?Xz(BO?8Xw=RD`J)As?rV^H2 zKw&L$WDY5s-7pr9oPiL%Vn~ee?^)PqhmBSKpU-~;S-PDpO_Q5P$jdA_ zIZ0MNNKQUPR-PqOUL{xFAV>Z|&3Diz)?lAlhy5an+e9yJ7ehEm%V{x&0r3C^#j`jd zp2v`Q1;gTX91?G0)Mw!lETi39@Ihuln3mS#c8;RdyNmt@$Um~L%+Z8+27}xcI3iBs z01lF+S&_#bL>qr1l7C_d!wA#I3LK(b1S8baC?D*N(!&^6Zh-m@(hAg;J>p$>LcyKy zVx@w^3daA1-eU?n-zKoTC>oZ|TK6&~?haA{DL+NP{3>DNnTDCA1p;N%RWocq@3IG5I2mk;8K>!U@s4Rai6aWBpD*yl>ldx$P zlg~g6f1Ozgd{oudKPR)i$?(_$Aq?w?1hR)635bLwNHhsZSd0|mW#%OrnI+D=Aqll= zEmmu_ty?Wx*Q#ixRZtQjifh$c+^VhGO{=!ns-L#~7X6B*|MT9=WReU5@+0@Xcb9X| z@;}SH^V}B)4-wHE{>V++dAKwqq!}sAC}~D#f1}BfW{iA}byFedDm>0c{OV(Fa&w-H zjhDvb<_SDenn`Y+%v0QS15cI4tMEx~8q3pU{>chYcX7U(9^e@Y&verSE^yNxE|i`k zX^Istann@Jb#p0~xv7%N<#U!av!$6cj1KZ#h3C0=zQPM+#zHsEL zd7dvkMHP;@sZ|u(%EmDInB&rHQ@F!TL9UgiQzmvPyj|h1yXkzH+s+rrf^Uet7rN;a zzDPbVlDCV+G#4rSO(wNA9M+>%K`j>3V@#gvniZAn>egShK zT)UDfr|vv$n^qpw!mZ_vMl=v^UCcDRDiV$vTG&{x1>?GlFJW>9Bdx7^lxbpJB-&cu z8rA$ky}To;wYTfh@;Y-6D_#CbM>rVK{7h3aO{}d>jLRtT)6%&3bgLhC#7F#HR(k*3oWAuBITkJF@-OEoT>1*NkJk%f3}YXn&a}l zE*fMSVUZ8(M)|rmwV0BdKBciun=^kwV?4w(Iw+!7rwuCnEp*on?q-^IOf63zvI;vZ zvU7DHnqsP7X4TyMoItyLLzlpb-Y&~x3h#hfFzAa1q24rxrxgsOQkcnmY;Afc69@2D z3rn_`iOJUVnC^>5e*;EWc|EWQAXW!j^_U?mTg2$OsXc1L?QsKibuENZh8mpB z@s<{Wde+9}@V4eISYIoH$6&~Dk%?hiyEf9EJ`1;&HrbpcZW z;|BUdS9{VQyo2U08Mxch#R^}B<=ZUw6P{Vsru(+W#BTEohBACif#9@C$g&XhtNDz$ z7Bo?i9gD=HKHbFnFuk)~_Zhn19B~CLxIsE^W~lT_tMKI@)fi|EYeqb(57qJD6+>i( zrDM8L(+M~kqNde)e>4<`#RS4|qQTT4PL{xHe5&6PA# zvN+qX2XzUa(G1GML?sqClLc7Dm&?}%*`hk&J7!}>uLr0VdW*>s4{r}Z{HYmTCfytk zJ#0j~QWi0_jiu#?Ni{MeK?>$b#Q^b-B#~8V{SxPdR6vq_UK+8Qa6F`^0=3O#%kI}D zTPWL;fiG|9f9@uS3SXh{cNM-8A>J2h?@9|sOl1WbgH&erEa*XVHWOU7plH#pncAH` zYt}5Lx{SFindnY9^kj9;l4iCvbNaWMEn8(ylgX_zCcad4lO!}p2rW5rLh02{lGfZ~ z(>g{V>8CYMXqBD_t#kSp&zHq#9mnDm4We0{bNhE$e;~UoK4EjGyG@eR!V{KO7B`x) z+k(EDm{%s#RC=18QRy9eSEXKhSf$_7A5?mro>1u$`j$!;(>GOmkRDR$a=r>1pHQhO zi@vAQx9KvKb`Y}e_f`G@U#;>re67OQ$;b67|B!D``A2*M((%!Snm${I?Ns?jz6m0v zO9;1ae_UBvifTpWAM?%d?ex(!M+F7Q%D3>XD&NMpt9%Fl1kojP*`V;9D&NI-tGtWv zQTbl}sWkVgyqm98`DgS7azX#fHSw?!2J>Z?0ADij*NA#FC95K8o zKMgGq_G;lSOp79+MkJb*d215c)oVn&EePaZf4vilIN0T#otoEGhEk$`|5eTBpb3$5|w@urodz*DV>@~DdyQFPzN5E(+%MY6cc{JoT+B5@= zf9{=`vD}{NZI4E<(CG3)(_ONc1+dZtz{(Qi5Zfz7t2YpXa-t$54C9w2UM&jN58!<%g5e&?{A+3|ZYNjr$VyTZL&T zknvWUMc9x5m3zdE_N#n=4=UWN^21{Ie@FNbvUPv7tc*srE(w_`KT2f}*NOJm@!_7_}&zBUy}k+xx3gZ%ZUv;gzWI8-;(X@@xD6 z67lMwuEhjSUODWF>%q2gtU!wiwGJ(8h||R}M_`t4jCHl}cO?=l3!{ot`E`Cn;oqtJ zd;WvUf8;-5tivk!RDP4+Qu)vPe>Muvj3tgr@AEq7Vp3l|SICRQ`}}NANs)tVfBP>=B_4sxsqA$wUj1GvJA1mr-5?<^%`>E?ul_ z4*`ckKvROS4-(GKaNaLGf5zpD9oX{=zBVo|tOa-RcE4swNresza!!B3H|zz4a{V%T zU<@^{Acq-|mHjs{xdpWuvE#%MsMTmQF)e$E&g1|)v7l<`{M6-5$XFj>heq;KF5?4af>k_}HGw*$t zoDgP)+#X2~s!v|1rI`{j-gLd;30F^k4-C9k?_#;rNfs>T@$a}?B6%<6IqLCV?j<6v zRv=lOD3qCI92fn?NpY;iC~;bD$<{TdeqTu&SZoG~x=072e82TYJ2bLQi`7S>dQDId!3F^Su&~}~Bt8clBjwEs)MeeL zIYV2mdtFaIjD}nTm8Z)(;I8Xvcy;)K5z&&P15sP2lW02?5|M*EbOC*Xm@dRu7F|R+ zaze*@jvUv`ei+ai3lrwBJJ=;U-J{n$B zypNQkl6~YXD&0pT_Lw_-7wrUcqMe47UK&d$gNNxfh4S$>gRaC#kwufPqVExzZ^9Fs zZ^BiU`6hhX(EEM*0eXa+{p2PE&!xrPG_oGesD`44e`o|=MpxK9_HN3laL8j!g%kb5 zJvm#lc&CyCNvfI(8LDY0{iGu^suYKk!#Pol_r&X9Njc&fj!rLOW!9Y9)~R# zLQdY*_ijlyO{svCQ=59oTcOw%xN=<{=b<}j)@bVUICEWdFWgTjRb+dzyJ?#JHX7zp zM$PJ`lQ(!2>6*S_hl_Xhz2H&0DPPoLGu5(!e@3I-1h&tmk+d1m*a8!3G?kiZCi$Q! zKb=CYP)C4Hr}JnHZN-crzCv_9MW_pX7g5wyVG9J5)we@Q*>ncYr#t8;1 zgp%MISalcO4dslaZM2K-0Y5nuqkFN!4jMuFDcuLPF2%09@#e&HDgBIo4l~^kI;G_3 zf5SAVLfaL}Q|JMO_OL>GiKcu(qw%89R6as86sr7;h7YjGgY-}WW4{71L1#k|OyOuK zJwP)UW*y&4Gn;Y>?2k}kldYt2Kfxi2AH`@1!pW_P;nKmwwgXg_MG4H=(=gY8wi9A@ z0q0)_;x3>ncxb7*q;Bega`vNFH5Dg42hbyG$fm3#G+m*C zQwE6GOAjYRdArQnNSY%u!59iW{5k=$PBsIKHrzqOVAQdQC?3R=8Svk^c%A~^Sq8tUe}#XCtY@i-6Ak}TS= z!vXhrv!vg8Q%r(80pJ^9y_={2e_YMN_KWa-8bF@3U;$j{PSf+TeM*|jge_f|FBZ&7 zS`p}t+S5yU zO~pA&d+4-!Zs?_DP0mNCvdNaS90tv)f;nN;>c$?bvEt?m#7!9V^qsV#f0tG^^-^t< ze4o)nXZBE?M3{Ogu%SW`4XtXba6L_V9wleBg?Epuv764?fOsTsx<3g_$aYf&;=yu6gbh&S7TSZzv=>q3A*}BOrEX}e2XO2K zwfzX(2VjnaFx$hR_6Tru2=4!wX~cE_aswRmS^6b(y9LSXIWsi0(PTOd__?s#8hV~y zfUzs+OnTAusw*(}W%@Pxu7_D)rdLcjA5H<_Ffb_q7=xXEW5PKXfBgJ51?L)ax%#lL zD`|QBuT*H6La!;bQlWaHBQynleUg{cClM`IsPPPi)(tNN+1KffLYLJXBg(C;CCE)D6`ANPnLG#Z>><14wHwJ{+r7gk z-iE2O`&pW1<_gjLVQl<7g31f9#fxyVmym~^r#aA`us9Ffn|2TZhLi1c8llkJJoz&a$&#No68ZUMe{3#pbxkj|N@i}eU>%UG zaGqp^0A98-AQQA4D72IEM7R?92t&MXioh>k>7{l!)%i^W#(F5)Lot*p9=miI9%m25 z#lg1iqT!aSZSyFP?&`ZvHtmp3m-*&#J-P=%ZbF)kg1aag=F<(JO9giss<+Eh3Tyz_ z2$pd}HKU*ke-D%~o!*A>-l0<==`wl`l->ue50JV)1f>sK4^!D|pqJ@%HvNVE3XN?-VelUP4Hh4Ty!Jl*9Xms3DP>;+idF`@26P4VSL4f? z=Y~^$ME?b8#1tASpVKIXK31sp2$d@o?4#MFq~Tmff6%Rf8D#ceXzPCH3c87 z=1?Cj=NPmSTO>0@BiQ&S{VS0vZbqNLHGi}nWmiLSDax&;1@@b0L`kVxY<2GH`v}17 zLa5r-pYg0*{@-Z-5Aps}RD1tM#f#)ipQk_xqA5+}W9~hsCi3ZjpfSniQ_Z5r2FT{o z|C)u)fBmM9A%`RW?>$1f+|TqV7k2tI!E_B)iKdmJV&rgFe_87^x0qt3 zWnOIsBxg!0rzI8WWU(z19sBMRq+@4CLd~n8RUHY7E~puY2-}{Fl&rGNm7?SVC9808 zLC;p<;$o*6>C-gM3cE6zGb{5pUvAEu(##30a5lR$DT6c9K8i9Zi-*a4R#B-+j>tj~ zw*K9~lj%pq{{gdrRk{KVZ*Cb8wgCVDU;_XEIFqnx7L!0)4U_EH7=M*gTT2^36#mX; zv#aS= zCG!2Gbf`4J%e!i@`G1zM-pF((?r70YWPG7Tzb|$CMdaQ3U?9($iPVhqKB!dX9|`r^ z?DlEW>1gYO;2vacNmy*CR2~n{no@rg3?zh&tR<2Yp_PdzN!JJ^EZN#2%h#$o%vF{W zf=_8G^+6(-np@t@l(zZL5PnJ-aJQ07cD z#$t&NtYa3Riz=Je$;ZbTDq1j3zvN5bWLuUp@eK z@XXrhtQ)M5xbm9cM1KHKO9KQ7000OG0000%03Ax$;uZ%009y_K049^LX%>?}S`~k3 zV;ff$J!4B6SsurZVkfm@7sWBHEZG(bG(g-2yfsm4*}+?J($*bY6L}JOq>e_34P_~i zmVGHuD3r28*B*CUi}$dH#|Lxm;V1z8kTJRN^Q1UcEUMJk2i$Xt#<#ZB41CBvqQtq6|c6A^q; z{)^+8Fg_K*r}3ExbbMB%XH|So=FdlP5?_$vwu9X34S5)v|wM7Ayr? z+OiCLBCnT9MoGbmi*sX>(^D&p^HXyxmu53lEAtC;>6wcPqSM#)n|dm*Te;Lc4OqER z1#J@rtK{gGv!v(ChJquP=Vl+7npmivI+C;XY~ENb8TO^ZhG=+Z%tGp6GjGsD=t0vm zoeK(@$>jg1(wJ1YgK6>9#3re>32$n`GTTU9fX04=Q!b z){8~MPF>cW^)Y(2K~0-LN8|gU1+6`2IQ!$V5^rSdF>j`~*UVhm)us+WuzT>=@-(yS-8+Jyq$u)UQke{khXSInY<+4z53v-d9iY>;`i zZ09fOrFBY-p(owf0HxvKwhg0H(sRb7nKMd`f<8~FWUQ5K)7eU8_Wn)%;Odqm)!B4) zT!BI#yY^U}+FUb=etbeD7lH`$j=pvyqZj=`X}67y!cAjp(=n`)8}@+ZMoVFIlr&@L zSArMAe%}+za8iqN=|g`)AmLrK^R=Sh)u!>K7s)Ip)Fn zLfKw3WRu0eudGJogoaT(sNusnui}RqCh)R`$MJ-Qk7HIt8q>Vndo64D5nj=-iZ$Ny zgDl3&Wqxc)kRVw3rjMW!2OR=(b!z$b&-;TO7v#ZyQ zHD}+}ykFw?zr*{>!|}m`1$yj2;~RHtt~1`S&<`q$|0FL^R#w6AJG%CMiAfW43cEg> zKG2gJ7+Uh~5Zjo?(O-BR#u|3({hhxN!rnJP+Z!4MC;xv>EArYz+I{lY$mPu8o*&xF z!qO1Db{2>aN<#~ki&@>FxnTV2xG)N3eY8+K?d^2M(+x9|Xw=v1I}7V};g&Q&*U?r! z@+6-%HfOJi$p+l%e@m&ny4yvM$J32*rRV!qU_4#c^Q8m!ys{k~yt2P?w@Qw&;RW%s zU0|x5twVo^Ea4QtlFssrtQp;S0Oz3KgIqOXkn0caStt2p6QmsG9(y9khq!t_XN7Yx zQHAoFt9pTBgfq~G0Pe*{C~2M&K8i8UVqn}i@Gvz+HzEcS$vbGOTRB2n;CEGkG+WT` zS~~7&`<6r!T0&w1lfKRW5=rHJJCUrQxr#t0F;ss=a3(RFtRi$iumg2j{t8#ovV+KS z6|G!p6|_ZIiSA%`sEW?*nmauRag5WI zL9`=*6O8HvhOmiY*R@L?>6&Y|F~#t(R`3iiG8aueb(31>7?u;T`1+htJN#btbq_^pi39OilUH01>> zy55Y`*pFbzW&arE5R_GwI8E}T`>e0?q?BG~GJ3j#froluMliXZZ0@b#z1!|>5l&Ip zvqzb=X`*Bp{aKew%sX2{>%_8)rc&byt`f<|{TJH!wI$yZKJK$TDK>i;r~5KPlA3>k z3w;D1+8*i)JXOK{b@b!(81ykn|1^5oL7$@Zp`NXt8iO7@i4|f5(S@hnO44|_giEu_ zr3K2r5mliJ9e%s`bY7qt3EX5dI#@yCC4>{NqiH)CO}eWNxf{`;yBMxwWLvW5msK>y za&l|yeY=<9%$o;@KTgmmNa9JRYK0<2r0==ilQrU#$kq}?E=Rifzu^|?HKtt3l<{)lOUxelWK({e_J?ZTcA`yydzeGw$KYAAcz)} zixdh$N$}sYtaYL6lIb?SYQhw@y|JPX6Afz*_qk% z^Uv3B0MeM3(11i8ElCNDNJvN_9Y8PcarANA7m@)99D^JWIEEzzFd{+1BaX)$8HQTx zwN{KIe;L}dhM7;~O?joDCX|Af7&F$_Wql>9>FS(p7FBbIw1+iavql&uI^EU()v(zs zWqLzhiwwRoV?||X6pY!;^<~w3E-x2|6V4inTv(J%O`JSN?69Evlb9B3Yqf11ij80rmu!IDiYw_$09&N0T&vJL%gy zIwC-_qO8rx8}_H*c*3xDE>++jYs#(^&)cL}QesInM5?*RAT1c1rlO8(qI_B^bb3Ut ze}V|(LJ%P|aXbxT91|RqK}_KpLz`P_7zSM(d7-cA#+H6WJ+vMt3gRlR3CCurkX;Q- z9|PZVoS?hP0&@z8TC4mh+?o|jj-l^NJyuOjj>XKDY^sN2I!=&2ebZ3wyI0YPMc^n= zoym%#7K@RABvol|6^+s5wCSd$6%y1{f1<+RSzJ~493tEyt{-7RNv%+cIB z6xzF^X3aUYWBJgjwt1(kntRov{W`a`fbu_lFnY*gVES0c%rfR4!j@e>_IcF4MN5yP{Sq>U{h!zUJJ=cAD3_if3PW< zOvGcjPSzaM_wd6y!Qhv(JbD0}Z6d z$KU}3005f{002CbAf+Uejour7#a8Q5+eQ@r))uTIi^RB?gtnv(H359V+>&6MBn6sV zad28Ev?jgDLU9#rIU~zWUZIcBw@7E&At}?O|2osR=-<9WJ3T8oU}A$zCNuq`-97v1 zoNqs!Jvx8>`|Aq;b9f+Q2#Y7^k&zL>B1cY!ge4hSTn^$2u5x@N7RwxeD+2bh3>nur zt_N^~W6owHmsWBlMDC8uk^28sva*DPdS|*2=ndS1nh`63*8( zwYs5NhFG_ZlAy~FS#Fvgb+G-=qSCb zp(8~Qj-Y^2gisZO(g~3&Maoj73J1Z^q)9K*5fH%=N+KeS5Q?s2zee_hA?({;06fkFE zOviFf>)qNmI9=U}bRy7Na9+LbOt5o83 zPBoU@ziZ`(0^NjBNf)F%!?<4Hd-V`DcAakr9PbI=7Qj_0i|*s>r^3ia(Sah_oKIK> znQOFX2hK=^S??TwWO`t;$%oQ{hqWf&FzHA`q&bPq2Y*7EPUt`^0+jSs60NfO;FnS9 zj)wOfOW*kq6hFl-aFR-DTmex3^7AtEJ3VM#yqJlukx3zUS@|t{~h1)*b#ye>wlA;qQw#$z%_gz$ukQ+XvE|LNt&mkIA zQ;Pv+h7XF&bd(uw54e!7kDayK4df;W9KLk}XM7l#aCBVUhUUig!JHd77vlXpbgB?l zIAXJ>R?Ck;RmtC7mAQ*_$@lZFl@|?;H^a-5-ka09$Tp?1Iu2X%{`+^!4~zKu_2*yw z(jBM!h4xooY(=bZ-FiCSzDt#h8roHe&3Hd_yUpliRol#7 zAS5FMLFd=XRym~iW~EM6kv*|eR5vlr&eX@~mNN5oXtM|Ev=|+cg`}BSXf9w2N_AZ` zSdP@JXnKUynGt;H8~0*l=SOt0rc@%`)R@-XmsPML4ZVe3pB*6Qi_AX$+;T9c1Gyj4 z;P=5IyE{8MW*Ej7(_5(U<->qRgXbCH3D?2l%cJ+ja}jej_U(jylU^Ic=l64w=j95v zbQQbjIPr7N_g!N>2>EC~X`eF@e}46SrrcJo?`5Zi#D(%>`Up>^ZI~U}AgeZ0eGc|}$J zkUqY+<$Io#Oqnl1x}_ml%`m$oW9n^h`E{@H63ckS(6bH&`3 z@+GvNuOYPg-F*556nCIJ=&mO7kI^%Vg0J^jrx%2pPkeOP)O_;l{B<=+{|gN~eF5A? zI(Vsy;)h(F$wU{`mw{`iiw2(6&yxjIucHPzkUZ0GeIIL!+oGaNbbl#Yh)OR80?n|1;0J!B3|psn6Pf!>R++GI)x*Mv zhGXk4_@oQ!a*H~A>q|*Vrx)-^>>I^Nr*De4?O2-_Jqmdq-;{Z3>=m!AJQ=PqO{)GYurw&9HxsH{d=NrD?pV2KaUx&rA1jyGuet%MKJj&bACJKtSH@gL? zx@>p3B&(ph-pQ!JX6Omp6!zE)7u)MQu`co^-&3yCj=l0trP@l4W~kUVNCJaZp|f@7 zb(gx0hwkF}A{Q?zjK#IqaH1+Vx3$-vAaeOLl`Yyp6O62q6?)IKtuyBHR-5O_?1iiA zKt482>mKY=_e~Yv*FNoj`-5zs26Z5lnS39EwKWc{HTY2MIyWzAS_%#?ZTDV~@uK=TWCAwO zO5t9KeSUYReglVutdPz$&;lhnE~9*c>mYTj$6zj>&pm6aZW(Xh6!&!AK?kv{7}f5G zF87Q`;708b?d5>zf!yR%$&2D&pWBrrcnVtDk@I7(f_GbJw_V^2_ z{hsv);Z-YPKgj&o(~N!9-ytn~Jl2NESC}47{hH9kd+t#PDdY@6~^V_ej~8 z7saZK7hSpvoRfDU2)F-=#bA={M(#m{I0zb&WY>#zUzor0To3e&K^ccU+a_v$9MwO2 zg*0R4W6EclP=Du6mvGNGL&_EW^5vhqJU>_I5bimjH0n>(h>Zqek2Mxf5;#(@tA-(t z=8Gp_(=hdd+da7f=O^E>kFYwz!{wA(atMEN+(_(x)S^Oss~OX#G`}~+b>XyL+VHE{ z)H3h*`m_4Ppm%wPQ22;uBk0HG&n(k_ZDhz&s^Y<-)uzT^KkU2BA>)buZ1ocdG3Uth z+o-S@Y=X?=r@hNyvlJxIDnHR5dsjQ~>4Wl%VM4bTZTH~&5xiAp&)X6NIB#Du$cbh= zNFi3HIxdv4oS0jR8~v-+7@2wlbI#kmsLTyP-s?#1T*<8+D;X6pNP2Mo7YhvW9b!Bf z)n%{vbBA7hNu)Uv9s*BkSvkO0ItvjUvNNpHl1q3*-Wtx6X!jUL)Fbq{d1y-ikp zk>Y{X*p->aMtyE*@9lve|2Nna4UHUl_q94D*3?PBn}HG;LlE?P14B}StN=7LW|YCY zG9`q@KgUCy1tS%bCrI)ijjHhvLPPTq*A5m@DoP=o1lr(W55W*_m~$R3?y!0N!|jtQZM!ZW#%c z`iG~a4{^$9&<7&}vXi8Na5Shz(gLc`;Pu;l;64TcR#Lob-V{@ZBB7x8TLkdoIw(?# z1RdU<1tR>ZGM^*_5-B-zH>qaR5)zc3MF8T#6jKbuMl*uLuc62zpFfVYfIWl?dc`2X z*;#3zDV%Du-Q@&z=b*?V^SLAC$|I>@@|*!c7ekd>b1KOH4e2;&XmpP7jd4`Sd)^S> zPNB-lc>{neog$?`^#We7n+yeg77zeDlj_nbf`Z6-D9Bs#|BqPmJ~h+4goDbfEg}Gk zT#8Etj9-)nL<%TU0W`z^U6huL0H8$_ml&u}`F84c zrDskYX>wsH1r!C@mQDiO&ET<7QP71V>ANXXp0c7G3c_Cef7_(`z_lfJV80*yHLgYN nvEpR}Ffj;rE}sKLhd|1%F5dI!=ox-rLy2 diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/res/drawable/ic_close_24px.xml b/packages/react-native-bridge/android/react-native-bridge/src/main/res/drawable/ic_close_24px.xml index a922efedc06ebb..a20716a20f105f 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/res/drawable/ic_close_24px.xml +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/res/drawable/ic_close_24px.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/res/layout/activity_gutenberg_web_view.xml b/packages/react-native-bridge/android/react-native-bridge/src/main/res/layout/activity_gutenberg_web_view.xml index 2dcf133412d64e..bd8c8503f89d07 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/res/layout/activity_gutenberg_web_view.xml +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/res/layout/activity_gutenberg_web_view.xml @@ -17,7 +17,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/status_bar_color" - app:titleTextColor="@color/white" + app:titleTextColor="@android:color/white" app:contentInsetEnd="@dimen/toolbar_content_offset_end" app:contentInsetLeft="@dimen/toolbar_content_offset" app:contentInsetRight="@dimen/toolbar_content_offset_end" @@ -36,7 +36,7 @@ android:id="@+id/foreground_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/white" + android:background="@android:color/white" android:gravity="center" android:paddingStart="@dimen/foreground_view_padding_large" android:paddingEnd="@dimen/foreground_view_padding_large" @@ -49,7 +49,7 @@ android:layout_marginBottom="@dimen/foreground_view_padding_medium" android:layout_marginTop="@dimen/foreground_view_padding_medium" android:src="@drawable/ube_failed" - android:contentDescription="@null" + android:contentDescription="@null" android:visibility="gone" /> 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 delta 20005 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 nvEpR}Ffj;rE}sKLhd|1%F5dI!=ox-rLy2 Date: Thu, 2 Sep 2021 13:06:07 +0800 Subject: [PATCH 108/214] Block Styles: Fix long strings of text without spaces overflow the block (#34222) --- packages/block-library/src/button/style.scss | 2 +- packages/block-library/src/embed/style.scss | 1 + packages/block-library/src/heading/style.scss | 3 +++ packages/block-library/src/list/style.scss | 11 ++++++++--- packages/block-library/src/paragraph/style.scss | 5 +++++ packages/block-library/src/pullquote/style.scss | 1 + packages/block-library/src/quote/style.scss | 2 ++ 7 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/button/style.scss b/packages/block-library/src/button/style.scss index fb85a6cdd6ce26..4b778ed3be541f 100644 --- a/packages/block-library/src/button/style.scss +++ b/packages/block-library/src/button/style.scss @@ -14,7 +14,7 @@ $blocks-block__margin: 0.5em; padding: calc(0.667em + 2px) calc(1.333em + 2px); // The extra 2px are added to size solids the same as the outline versions. text-align: center; text-decoration: none; - overflow-wrap: break-word; + word-break: break-word; // overflow-wrap doesn't work well if a link is wrapped in the div, so use word-break here. box-sizing: border-box; &:hover, diff --git a/packages/block-library/src/embed/style.scss b/packages/block-library/src/embed/style.scss index f416d1d73c80ec..30ab048529ef26 100644 --- a/packages/block-library/src/embed/style.scss +++ b/packages/block-library/src/embed/style.scss @@ -21,6 +21,7 @@ .wp-block-embed { margin: 0 0 1em 0; + overflow-wrap: break-word; // Break long strings of text without spaces so they don't overflow the block. // Supply caption styles to embeds, even if the theme hasn't opted in. // Reason being: the new markup, figcaptions, are not likely to be styled in the majority of existing themes, diff --git a/packages/block-library/src/heading/style.scss b/packages/block-library/src/heading/style.scss index 31a6989c39c817..e07cdb2f11193f 100644 --- a/packages/block-library/src/heading/style.scss +++ b/packages/block-library/src/heading/style.scss @@ -4,6 +4,9 @@ h3, h4, h5, h6 { + // Break long strings of text without spaces so they don't overflow the block. + overflow-wrap: break-word; + &.has-background { padding: $block-bg-padding--v $block-bg-padding--h; } diff --git a/packages/block-library/src/list/style.scss b/packages/block-library/src/list/style.scss index 04f579042196fe..2bc29a349e4f42 100644 --- a/packages/block-library/src/list/style.scss +++ b/packages/block-library/src/list/style.scss @@ -1,4 +1,9 @@ -ol.has-background, -ul.has-background { - padding: $block-bg-padding--v $block-bg-padding--h; +ol, +ul { + // Break long strings of text without spaces so they don't overflow the block. + overflow-wrap: break-word; + + &.has-background { + padding: $block-bg-padding--v $block-bg-padding--h; + } } diff --git a/packages/block-library/src/paragraph/style.scss b/packages/block-library/src/paragraph/style.scss index eb320b1853c2a9..23e4de9c320ca1 100644 --- a/packages/block-library/src/paragraph/style.scss +++ b/packages/block-library/src/paragraph/style.scss @@ -28,6 +28,11 @@ font-style: normal; } +p { + // Break long strings of text without spaces so they don't overflow the block. + overflow-wrap: break-word; +} + // Prevent the dropcap from breaking out of the box when a background is applied. p.has-drop-cap.has-background { overflow: hidden; diff --git a/packages/block-library/src/pullquote/style.scss b/packages/block-library/src/pullquote/style.scss index 3466ce56fa6c00..e1c98fa5ec0c40 100644 --- a/packages/block-library/src/pullquote/style.scss +++ b/packages/block-library/src/pullquote/style.scss @@ -2,6 +2,7 @@ margin: 0 0 1em 0; padding: 3em 0; text-align: center; // Default text-alignment where the `textAlign` attribute value isn't specified. + overflow-wrap: break-word; // Break long strings of text without spaces so they don't overflow the block. p, blockquote, diff --git a/packages/block-library/src/quote/style.scss b/packages/block-library/src/quote/style.scss index e6a6dfea5a4855..e10ccb18bec7c9 100644 --- a/packages/block-library/src/quote/style.scss +++ b/packages/block-library/src/quote/style.scss @@ -1,4 +1,6 @@ .wp-block-quote { + overflow-wrap: break-word; // Break long strings of text without spaces so they don't overflow the block. + &.is-style-large, &.is-large { margin-bottom: 1em; From 89abda19117258eb843ed3478fd15b00901c1013 Mon Sep 17 00:00:00 2001 From: annezazu Date: Wed, 1 Sep 2021 23:55:05 -0600 Subject: [PATCH 109/214] Update bug form to use drop downs (#34458) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update bug form to use dropdowns This is a minor change that removes the checkboxes since that causes a todo list functionality to be triggered in the UI, making it hard for contributors to keep track of what's happening. * Fix some spacing * Fix spacing again Messed up the second dropdown item * Fix texts that need wrapping * Make dropdowns required Based on feedback, this updates the form to make the dropdowns required. Co-authored-by: Héctor <27339341+priethor@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/Bug_report.yml | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.yml b/.github/ISSUE_TEMPLATE/Bug_report.yml index a104c70d1072f2..df4e13eb58d91e 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.yml +++ b/.github/ISSUE_TEMPLATE/Bug_report.yml @@ -1,6 +1,6 @@ name: Bug report description: Report a bug with the WordPress block editor or Gutenberg plugin -title: '' +title: '' body: - type: markdown attributes: @@ -49,10 +49,25 @@ body: validations: required: false - - type: checkboxes + - type: dropdown + id: existing attributes: - label: Pre-checks - description: Please check if the bug has already been reported by searching https://github.com/WordPress/gutenberg/issues and make sure the bug is not related to another plugin. + label: Please confirm that you have searched existing issues in the repo. + description: You can do this by searching https://github.com/WordPress/gutenberg/issues and making sure the bug is not related to another plugin. + multiple: true options: - - label: I have searched the existing issues. - - label: I have tested with all plugins deactivated except Gutenberg. + - 'Yes' + - 'No' + validations: + required: true + + - type: dropdown + id: plugins + attributes: + label: Please confirm that you have tested with all plugins deactivated except Gutenberg. + multiple: true + options: + - 'Yes' + - 'No' + validations: + required: true From 5b704ad8c2ade6190f3e25f5b6af96b0ba86fa4d Mon Sep 17 00:00:00 2001 From: Anton Vlasenko <43744263+anton-vlasenko@users.noreply.github.com> Date: Thu, 2 Sep 2021 09:09:47 +0200 Subject: [PATCH 110/214] For the purposes of removing the "experimental" status on the Navigation Editor, we agreed we would disable the theme opt-in from the screen until core developers have had enough time to improve the feature. That means no additional blocks. (#34444) --- lib/navigation-page.php | 5 ++++- lib/navigation.php | 10 ++++++++-- packages/edit-navigation/README.md | 2 ++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/navigation-page.php b/lib/navigation-page.php index 31133a90d7ee90..568fca8cb3bbfa 100644 --- a/lib/navigation-page.php +++ b/lib/navigation-page.php @@ -41,7 +41,10 @@ function gutenberg_navigation_init( $hook ) { $settings = array_merge( gutenberg_get_default_block_editor_settings(), array( - 'blockNavMenus' => get_theme_support( 'block-nav-menus' ), + 'blockNavMenus' => false, + // We should uncomment the line below when the block-nav-menus feature becomes stable. + // @see https://github.com/WordPress/gutenberg/issues/34265. + /*'blockNavMenus' => get_theme_support( 'block-nav-menus' ),*/ ) ); $settings = gutenberg_experimental_global_styles_settings( $settings ); diff --git a/lib/navigation.php b/lib/navigation.php index 7bfe3416647c6e..0a4ddab251efa1 100644 --- a/lib/navigation.php +++ b/lib/navigation.php @@ -152,7 +152,10 @@ function gutenberg_output_block_nav_menu_item( $item_output, $item, $depth, $arg * @return array Updated menu items, sorted by each menu item's menu order. */ function gutenberg_remove_block_nav_menu_items( $menu_items ) { - if ( current_theme_supports( 'block-nav-menus' ) ) { + // We should uncomment the line below when the block-nav-menus feature becomes stable. + // @see https://github.com/WordPress/gutenberg/issues/34265. + /*if ( current_theme_supports( 'block-nav-menus' ) ) {*/ + if ( false ) { return $menu_items; } @@ -246,7 +249,10 @@ function gutenberg_convert_menu_items_to_blocks( * @return string|null Nav menu output to short-circuit with. */ function gutenberg_output_block_nav_menu( $output, $args ) { - if ( ! current_theme_supports( 'block-nav-menus' ) ) { + // We should uncomment the line below when the block-nav-menus feature becomes stable. + // @see https://github.com/WordPress/gutenberg/issues/34265. + /*if ( ! current_theme_supports( 'block-nav-menus' ) ) {*/ + if ( true ) { return null; } diff --git a/packages/edit-navigation/README.md b/packages/edit-navigation/README.md index 1ee150967f8697..1a405fdd4f1e2d 100644 --- a/packages/edit-navigation/README.md +++ b/packages/edit-navigation/README.md @@ -41,6 +41,8 @@ Moreover, when the navigation is rendered on the front of the site the system co ### Block-based Mode +**Important**: block-based mode has been temporarily ***disabled*** until it becomes stable. So, if a theme declares support for the `block-nav-menus` feature it will not affect the frontend. + If desired, themes are able to opt into _rendering_ complete block-based menus using the Navigation Editor. This allows for arbitrarily complex navigation block structures to be used in an existing theme whilst still ensuring the navigation data is still _saved_ to the existing (post type powered) Menus system. Themes can opt into this behaviour by declaring: From b029315cf426fdf18efd82817db417cbe7ae7a30 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Thu, 2 Sep 2021 09:27:32 +0200 Subject: [PATCH 111/214] Navigation: Use gap instead of margin. (#32367) * Use gap instead of margin. * Unset classic margins. * Address feedback. --- .../block-library/src/navigation/editor.scss | 7 +++++ .../block-library/src/navigation/style.scss | 26 +++++-------------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss index e85c873c700e1b..ba4212cf67777c 100644 --- a/packages/block-library/src/navigation/editor.scss +++ b/packages/block-library/src/navigation/editor.scss @@ -11,6 +11,13 @@ margin-left: 0; padding-left: 0; } + + // Revert any left/right margins. + // This also makes it work with classic theme auto margins. + .wp-block-navigation-item.wp-block { + margin-left: revert; + margin-right: revert; + } } // This element is inline on the frontend but needs to be editable, therefore inline-block, in the editor. diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index bfccc9cb142bf0..5d188709b39801 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -208,19 +208,12 @@ /** * Margins - * @todo: refactor this to use gap. */ // Menu items with no background. -.wp-block-page-list, -.wp-block-page-list > .wp-block-navigation-item, -.wp-block-navigation__container > .wp-block-navigation-item { - margin: 0 2em 0 0; - - // Margin of right-most margin should be zero, for right aligned or justified items. - &:last-child { - margin-right: 0; - } +.wp-block-navigation .wp-block-page-list, +.wp-block-navigation__container { + gap: 0.5em 2em; } // Menu items with background. @@ -228,15 +221,9 @@ // That way if padding is set in theme.json, it still wins. // https://css-tricks.com/almanac/selectors/w/where/ .wp-block-navigation:where(.has-background) { - .wp-block-page-list, - .wp-block-page-list > .wp-block-navigation-item, - .wp-block-navigation__container > .wp-block-navigation-item { - margin: 0 0.5em 0 0; - - // Don't show right margin on the last child. - &:last-child { - margin: 0; - } + .wp-block-navigation .wp-block-page-list, + .wp-block-navigation__container { + gap: 0 0.5em; } } @@ -357,7 +344,6 @@ align-items: flex-end; .wp-block-navigation-item { - margin-right: 0; justify-content: flex-end; } } From 92662feeb220336fb28f8b79869cb251a7db4e27 Mon Sep 17 00:00:00 2001 From: Marco Ciampini <marco.ciampo@gmail.com> Date: Thu, 2 Sep 2021 10:36:07 +0200 Subject: [PATCH 112/214] Components: Rename `PolymorphicComponent*` types to `WordPressComponent*` (#34330) --- packages/components/CHANGELOG.md | 4 ++++ packages/components/src/base-field/hook.js | 2 +- .../components/src/card/card-body/component.js | 4 ++-- packages/components/src/card/card-body/hook.js | 2 +- .../components/src/card/card-divider/component.js | 4 ++-- packages/components/src/card/card-divider/hook.js | 2 +- .../components/src/card/card-footer/component.js | 4 ++-- packages/components/src/card/card-footer/hook.js | 2 +- .../components/src/card/card-header/component.js | 4 ++-- packages/components/src/card/card-header/hook.js | 2 +- packages/components/src/card/card-media/hook.js | 2 +- packages/components/src/card/card/component.js | 4 ++-- packages/components/src/card/card/hook.js | 6 +++--- packages/components/src/divider/component.tsx | 9 ++++++--- packages/components/src/elevation/hook.js | 2 +- packages/components/src/flex/flex-block/hook.js | 2 +- packages/components/src/flex/flex-item/hook.js | 2 +- packages/components/src/flex/flex/component.js | 4 ++-- packages/components/src/flex/flex/hook.js | 6 +++--- .../src/flyout/flyout-content/component.js | 4 ++-- packages/components/src/flyout/flyout/component.js | 4 ++-- packages/components/src/flyout/flyout/hook.js | 2 +- packages/components/src/grid/hook.js | 2 +- packages/components/src/h-stack/hook.js | 2 +- packages/components/src/heading/hook.ts | 5 ++--- .../components/src/input-control/input-field.tsx | 4 ++-- packages/components/src/input-control/label.tsx | 4 ++-- .../input-control/styles/input-control-styles.tsx | 4 ++-- packages/components/src/input-control/types.ts | 8 ++++---- .../src/item-group/item-group/component.tsx | 4 ++-- .../components/src/item-group/item-group/hook.ts | 4 ++-- .../components/src/item-group/item/component.tsx | 4 ++-- packages/components/src/item-group/item/hook.ts | 6 ++---- packages/components/src/scrollable/hook.js | 2 +- packages/components/src/select-control/index.tsx | 4 ++-- packages/components/src/spacer/hook.ts | 5 ++--- packages/components/src/surface/hook.js | 2 +- packages/components/src/text/hook.js | 2 +- .../src/toggle-group-control/component.tsx | 4 ++-- .../toggle-group-control-option.tsx | 4 ++-- packages/components/src/truncate/hook.js | 2 +- .../components/src/ui/color-picker/component.tsx | 4 ++-- .../components/src/ui/context/context-connect.js | 6 +++--- packages/components/src/ui/context/index.js | 2 +- ...morphic-component.ts => wordpress-component.ts} | 14 +++++++------- .../components/src/ui/control-group/component.js | 4 ++-- packages/components/src/ui/control-group/hook.js | 2 +- packages/components/src/ui/control-label/hook.js | 2 +- .../src/ui/form-group/form-group-content.js | 2 +- .../src/ui/form-group/form-group-label.js | 2 +- .../components/src/ui/form-group/form-group.js | 4 ++-- .../components/src/ui/form-group/use-form-group.js | 2 +- packages/components/src/ui/shortcut/component.tsx | 9 ++++++--- packages/components/src/ui/spinner/component.js | 4 ++-- packages/components/src/ui/tooltip/component.js | 4 ++-- packages/components/src/ui/tooltip/content.js | 4 ++-- .../components/src/ui/utils/create-component.tsx | 10 +++++----- .../src/ui/utils/test/create-component.js | 2 +- packages/components/src/v-stack/hook.js | 2 +- packages/components/src/view/component.js | 2 +- .../components/src/visually-hidden/component.js | 4 ++-- packages/components/src/z-stack/component.tsx | 4 ++-- 62 files changed, 122 insertions(+), 116 deletions(-) rename packages/components/src/ui/context/{polymorphic-component.ts => wordpress-component.ts} (79%) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 752245898aa451..03643e9e484c95 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -6,6 +6,10 @@ - Fixed RTL styles in `Flex` component ([#33729](https://github.com/WordPress/gutenberg/pull/33729)). +### Internal + +- Renamed `PolymorphicComponent*` types to `WordPressComponent*` ([#34330](https://github.com/WordPress/gutenberg/pull/34330)) + ## 16.0.0 (2021-08-23) ### Breaking Change diff --git a/packages/components/src/base-field/hook.js b/packages/components/src/base-field/hook.js index 14cb90c9e97eb5..e0eaf01dc5c9ca 100644 --- a/packages/components/src/base-field/hook.js +++ b/packages/components/src/base-field/hook.js @@ -23,7 +23,7 @@ import { useCx } from '../utils/hooks/use-cx'; /** @typedef {import('../flex/types').FlexProps & OwnProps} Props */ /** - * @param {import('../ui/context').PolymorphicComponentProps<Props, 'div'>} props + * @param {import('../ui/context').WordPressComponentProps<Props, 'div'>} props */ export function useBaseField( props ) { const { diff --git a/packages/components/src/card/card-body/component.js b/packages/components/src/card/card-body/component.js index 455ddc85ef5b20..73e2e337b4bebb 100644 --- a/packages/components/src/card/card-body/component.js +++ b/packages/components/src/card/card-body/component.js @@ -7,8 +7,8 @@ import { View } from '../../view'; import { useCardBody } from './hook'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').BodyProps, 'div'>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../../ui/context').WordPressComponentProps<import('../types').BodyProps, 'div'>} props + * @param {import('react').Ref<any>} forwardedRef */ function CardBody( props, forwardedRef ) { const { isScrollable, ...otherProps } = useCardBody( props ); diff --git a/packages/components/src/card/card-body/hook.js b/packages/components/src/card/card-body/hook.js index f7fdab10a0304b..2e62181b609848 100644 --- a/packages/components/src/card/card-body/hook.js +++ b/packages/components/src/card/card-body/hook.js @@ -11,7 +11,7 @@ import * as styles from '../styles'; import { useCx } from '../../utils/hooks/use-cx'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').BodyProps, 'div'>} props + * @param {import('../../ui/context').WordPressComponentProps<import('../types').BodyProps, 'div'>} props */ export function useCardBody( props ) { const { diff --git a/packages/components/src/card/card-divider/component.js b/packages/components/src/card/card-divider/component.js index d3cce90bc02ba0..058cc341633c2f 100644 --- a/packages/components/src/card/card-divider/component.js +++ b/packages/components/src/card/card-divider/component.js @@ -6,8 +6,8 @@ import { Divider } from '../../divider'; import { useCardDivider } from './hook'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../../divider').DividerProps, 'hr', false>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../../ui/context').WordPressComponentProps<import('../../divider').DividerProps, 'hr', false>} props + * @param {import('react').Ref<any>} forwardedRef */ function CardDivider( props, forwardedRef ) { const dividerProps = useCardDivider( props ); diff --git a/packages/components/src/card/card-divider/hook.js b/packages/components/src/card/card-divider/hook.js index 06961607c78849..091f99a12d079f 100644 --- a/packages/components/src/card/card-divider/hook.js +++ b/packages/components/src/card/card-divider/hook.js @@ -11,7 +11,7 @@ import * as styles from '../styles'; import { useCx } from '../../utils/hooks/use-cx'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../../divider').DividerProps, 'hr', false>} props + * @param {import('../../ui/context').WordPressComponentProps<import('../../divider').DividerProps, 'hr', false>} props */ export function useCardDivider( props ) { const { className, ...otherProps } = useContextSystem( diff --git a/packages/components/src/card/card-footer/component.js b/packages/components/src/card/card-footer/component.js index b5ef215eb3964a..0b0fa967a7d6c7 100644 --- a/packages/components/src/card/card-footer/component.js +++ b/packages/components/src/card/card-footer/component.js @@ -6,8 +6,8 @@ import { Flex } from '../../flex'; import { useCardFooter } from './hook'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').FooterProps, 'div'>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../../ui/context').WordPressComponentProps<import('../types').FooterProps, 'div'>} props + * @param {import('react').Ref<any>} forwardedRef */ function CardFooter( props, forwardedRef ) { const footerProps = useCardFooter( props ); diff --git a/packages/components/src/card/card-footer/hook.js b/packages/components/src/card/card-footer/hook.js index 772d0fc175b406..9c5ba8da75532b 100644 --- a/packages/components/src/card/card-footer/hook.js +++ b/packages/components/src/card/card-footer/hook.js @@ -11,7 +11,7 @@ import * as styles from '../styles'; import { useCx } from '../../utils/hooks/use-cx'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').FooterProps, 'div'>} props + * @param {import('../../ui/context').WordPressComponentProps<import('../types').FooterProps, 'div'>} props */ export function useCardFooter( props ) { const { diff --git a/packages/components/src/card/card-header/component.js b/packages/components/src/card/card-header/component.js index 621448d82856a1..d08a2e3438ea1c 100644 --- a/packages/components/src/card/card-header/component.js +++ b/packages/components/src/card/card-header/component.js @@ -6,8 +6,8 @@ import { Flex } from '../../flex'; import { useCardHeader } from './hook'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').HeaderProps, 'div'>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../../ui/context').WordPressComponentProps<import('../types').HeaderProps, 'div'>} props + * @param {import('react').Ref<any>} forwardedRef */ function CardHeader( props, forwardedRef ) { const headerProps = useCardHeader( props ); diff --git a/packages/components/src/card/card-header/hook.js b/packages/components/src/card/card-header/hook.js index 56a9b99972cabf..3c72511d6bff44 100644 --- a/packages/components/src/card/card-header/hook.js +++ b/packages/components/src/card/card-header/hook.js @@ -11,7 +11,7 @@ import * as styles from '../styles'; import { useCx } from '../../utils/hooks/use-cx'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').HeaderProps, 'div'>} props + * @param {import('../../ui/context').WordPressComponentProps<import('../types').HeaderProps, 'div'>} props */ export function useCardHeader( props ) { const { diff --git a/packages/components/src/card/card-media/hook.js b/packages/components/src/card/card-media/hook.js index 266e112090af16..ec42aabc09155c 100644 --- a/packages/components/src/card/card-media/hook.js +++ b/packages/components/src/card/card-media/hook.js @@ -11,7 +11,7 @@ import * as styles from '../styles'; import { useCx } from '../../utils/hooks/use-cx'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<{ children: import('react').ReactNode }, 'div'>} props + * @param {import('../../ui/context').WordPressComponentProps<{ children: import('react').ReactNode }, 'div'>} props */ export function useCardMedia( props ) { const { className, ...otherProps } = useContextSystem( props, 'CardMedia' ); diff --git a/packages/components/src/card/card/component.js b/packages/components/src/card/card/component.js index 9f8bcbde6d9bf1..45afbc28de136f 100644 --- a/packages/components/src/card/card/component.js +++ b/packages/components/src/card/card/component.js @@ -20,8 +20,8 @@ import CONFIG from '../../utils/config-values'; import { useCx } from '../../utils/hooks/use-cx'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').Props, 'div'>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../../ui/context').WordPressComponentProps<import('../types').Props, 'div'>} props + * @param {import('react').Ref<any>} forwardedRef */ function Card( props, forwardedRef ) { const { diff --git a/packages/components/src/card/card/hook.js b/packages/components/src/card/card/hook.js index b0e2d1a32325d5..d5a5cf0b478411 100644 --- a/packages/components/src/card/card/hook.js +++ b/packages/components/src/card/card/hook.js @@ -13,10 +13,10 @@ import * as styles from '../styles'; import { useCx } from '../../utils/hooks/use-cx'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').Props, 'div'>} props + * @param {import('../../ui/context').WordPressComponentProps<import('../types').Props, 'div'>} props */ function useDeprecatedProps( { elevation, isElevated, ...otherProps } ) { - /**@type {import('../../ui/context').PolymorphicComponentProps<import('../types').Props, 'div'>} */ + /**@type {import('../../ui/context').WordPressComponentProps<import('../types').Props, 'div'>} */ const propsToReturn = { ...otherProps, }; @@ -40,7 +40,7 @@ function useDeprecatedProps( { elevation, isElevated, ...otherProps } ) { } /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').Props, 'div'>} props + * @param {import('../../ui/context').WordPressComponentProps<import('../types').Props, 'div'>} props */ export function useCard( props ) { const { diff --git a/packages/components/src/divider/component.tsx b/packages/components/src/divider/component.tsx index 7204132d707ce9..085d2f5f012b71 100644 --- a/packages/components/src/divider/component.tsx +++ b/packages/components/src/divider/component.tsx @@ -9,13 +9,16 @@ import type { Ref } from 'react'; /** * Internal dependencies */ -import { contextConnect, useContextSystem } from '../ui/context'; -import type { PolymorphicComponentProps } from '../ui/context'; +import { + contextConnect, + useContextSystem, + WordPressComponentProps, +} from '../ui/context'; import { DividerView } from './styles'; import type { Props } from './types'; function Divider( - props: PolymorphicComponentProps< Props, 'hr', false >, + props: WordPressComponentProps< Props, 'hr', false >, forwardedRef: Ref< any > ) { const contextProps = useContextSystem( props, 'Divider' ); diff --git a/packages/components/src/elevation/hook.js b/packages/components/src/elevation/hook.js index 24f31fef0f7535..9256a2b2d99ba6 100644 --- a/packages/components/src/elevation/hook.js +++ b/packages/components/src/elevation/hook.js @@ -30,7 +30,7 @@ export function getBoxShadow( value ) { } /** - * @param {import('../ui/context').PolymorphicComponentProps<import('./types').Props, 'div'>} props + * @param {import('../ui/context').WordPressComponentProps<import('./types').Props, 'div'>} props */ export function useElevation( props ) { const { diff --git a/packages/components/src/flex/flex-block/hook.js b/packages/components/src/flex/flex-block/hook.js index 82d0e2abb6e9d0..deed81d8f28f7c 100644 --- a/packages/components/src/flex/flex-block/hook.js +++ b/packages/components/src/flex/flex-block/hook.js @@ -5,7 +5,7 @@ import { useContextSystem } from '../../ui/context'; import { useFlexItem } from '../flex-item'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').FlexBlockProps, 'div'>} props + * @param {import('../../ui/context').WordPressComponentProps<import('../types').FlexBlockProps, 'div'>} props */ export function useFlexBlock( props ) { const otherProps = useContextSystem( props, 'FlexBlock' ); diff --git a/packages/components/src/flex/flex-item/hook.js b/packages/components/src/flex/flex-item/hook.js index 136661aa7dcfbb..2c38e550db32cb 100644 --- a/packages/components/src/flex/flex-item/hook.js +++ b/packages/components/src/flex/flex-item/hook.js @@ -12,7 +12,7 @@ import * as styles from '../styles'; import { useCx } from '../../utils/hooks/use-cx'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').FlexItemProps, 'div'>} props + * @param {import('../../ui/context').WordPressComponentProps<import('../types').FlexItemProps, 'div'>} props */ export function useFlexItem( props ) { const { diff --git a/packages/components/src/flex/flex/component.js b/packages/components/src/flex/flex/component.js index 70c4e448985e0b..31983e0bc48710 100644 --- a/packages/components/src/flex/flex/component.js +++ b/packages/components/src/flex/flex/component.js @@ -7,8 +7,8 @@ import { FlexContext } from './../context'; import { View } from '../../view'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').FlexProps, 'div'>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../../ui/context').WordPressComponentProps<import('../types').FlexProps, 'div'>} props + * @param {import('react').Ref<any>} forwardedRef */ function Flex( props, forwardedRef ) { const { children, isColumn, ...otherProps } = useFlex( props ); diff --git a/packages/components/src/flex/flex/hook.js b/packages/components/src/flex/flex/hook.js index 35c3c7b10de23b..d0826099ec1125 100644 --- a/packages/components/src/flex/flex/hook.js +++ b/packages/components/src/flex/flex/hook.js @@ -20,8 +20,8 @@ import { useCx, rtl } from '../../utils'; /** * - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').FlexProps, 'div'>} props - * @return {import('../../ui/context').PolymorphicComponentProps<import('../types').FlexProps, 'div'>} Props with the deprecated props removed. + * @param {import('../../ui/context').WordPressComponentProps<import('../types').FlexProps, 'div'>} props + * @return {import('../../ui/context').WordPressComponentProps<import('../types').FlexProps, 'div'>} Props with the deprecated props removed. */ function useDeprecatedProps( { isReversed, ...otherProps } ) { if ( typeof isReversed !== 'undefined' ) { @@ -39,7 +39,7 @@ function useDeprecatedProps( { isReversed, ...otherProps } ) { } /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').FlexProps, 'div'>} props + * @param {import('../../ui/context').WordPressComponentProps<import('../types').FlexProps, 'div'>} props */ export function useFlex( props ) { const { diff --git a/packages/components/src/flyout/flyout-content/component.js b/packages/components/src/flyout/flyout-content/component.js index 9c0b41124d3170..943fe0d60c81fb 100644 --- a/packages/components/src/flyout/flyout-content/component.js +++ b/packages/components/src/flyout/flyout-content/component.js @@ -7,8 +7,8 @@ import { contextConnect, useContextSystem } from '../../ui/context'; /** * - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').ContentProps, 'div', false>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../../ui/context').WordPressComponentProps<import('../types').ContentProps, 'div', false>} props + * @param {import('react').Ref<any>} forwardedRef */ function FlyoutContent( props, forwardedRef ) { const { diff --git a/packages/components/src/flyout/flyout/component.js b/packages/components/src/flyout/flyout/component.js index e1d8de13888566..6d9c4a46153ea0 100644 --- a/packages/components/src/flyout/flyout/component.js +++ b/packages/components/src/flyout/flyout/component.js @@ -21,8 +21,8 @@ import { useFlyout } from './hook'; /** * - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').Props, 'div', false>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../../ui/context').WordPressComponentProps<import('../types').Props, 'div', false>} props + * @param {import('react').Ref<any>} forwardedRef */ function Flyout( props, forwardedRef ) { const { diff --git a/packages/components/src/flyout/flyout/hook.js b/packages/components/src/flyout/flyout/hook.js index 4cdb0299019758..d917c1011f4b91 100644 --- a/packages/components/src/flyout/flyout/hook.js +++ b/packages/components/src/flyout/flyout/hook.js @@ -10,7 +10,7 @@ import { usePopoverState } from 'reakit'; import { useContextSystem } from '../../ui/context'; /** - * @param {import('../../ui/context').PolymorphicComponentProps<import('../types').Props, 'div', false>} props + * @param {import('../../ui/context').WordPressComponentProps<import('../types').Props, 'div', false>} props */ export function useFlyout( props ) { const { diff --git a/packages/components/src/grid/hook.js b/packages/components/src/grid/hook.js index f2c80c49bd745a..b38b609f99b481 100644 --- a/packages/components/src/grid/hook.js +++ b/packages/components/src/grid/hook.js @@ -18,7 +18,7 @@ import CONFIG from '../utils/config-values'; import { useCx } from '../utils/hooks/use-cx'; /** - * @param {import('../ui/context').PolymorphicComponentProps<import('./types').Props, 'div'>} props + * @param {import('../ui/context').WordPressComponentProps<import('./types').Props, 'div'>} props */ export default function useGrid( props ) { const { diff --git a/packages/components/src/h-stack/hook.js b/packages/components/src/h-stack/hook.js index ceadb162e307b6..dd4795776f9534 100644 --- a/packages/components/src/h-stack/hook.js +++ b/packages/components/src/h-stack/hook.js @@ -8,7 +8,7 @@ import { getValidChildren } from '../ui/utils/get-valid-children'; /** * - * @param {import('../ui/context').PolymorphicComponentProps<import('./types').Props, 'div'>} props + * @param {import('../ui/context').WordPressComponentProps<import('./types').Props, 'div'>} props */ export function useHStack( props ) { const { diff --git a/packages/components/src/heading/hook.ts b/packages/components/src/heading/hook.ts index c967b9f0d4ac75..238e4e244c0f14 100644 --- a/packages/components/src/heading/hook.ts +++ b/packages/components/src/heading/hook.ts @@ -1,8 +1,7 @@ /** * Internal dependencies */ -import { useContextSystem } from '../ui/context'; -import type { PolymorphicComponentProps } from '../ui/context'; +import { useContextSystem, WordPressComponentProps } from '../ui/context'; import type { Props as TextProps } from '../text/types'; import { useText } from '../text'; import { getHeadingFontSize } from '../ui/utils/font-size'; @@ -50,7 +49,7 @@ export interface HeadingProps extends Omit< TextProps, 'size' > { } export function useHeading( - props: PolymorphicComponentProps< HeadingProps, 'h1' > + props: WordPressComponentProps< HeadingProps, 'h1' > ) { const { as: asProp, level = 2, ...otherProps } = useContextSystem( props, diff --git a/packages/components/src/input-control/input-field.tsx b/packages/components/src/input-control/input-field.tsx index 02455733de03c3..88596a8853e07c 100644 --- a/packages/components/src/input-control/input-field.tsx +++ b/packages/components/src/input-control/input-field.tsx @@ -22,7 +22,7 @@ import { UP, DOWN, ENTER } from '@wordpress/keycodes'; /** * Internal dependencies */ -import type { PolymorphicComponentProps } from '../ui/context'; +import type { WordPressComponentProps } from '../ui/context'; import { useDragCursor } from './utils'; import { Input } from './styles/input-control-styles'; import { useInputControlStateReducer } from './reducer/reducer'; @@ -53,7 +53,7 @@ function InputField( value: valueProp, type, ...props - }: PolymorphicComponentProps< InputFieldProps, 'input', false >, + }: WordPressComponentProps< InputFieldProps, 'input', false >, ref: Ref< HTMLInputElement > ) { const { diff --git a/packages/components/src/input-control/label.tsx b/packages/components/src/input-control/label.tsx index 537c335373b5d8..ea214707b012b8 100644 --- a/packages/components/src/input-control/label.tsx +++ b/packages/components/src/input-control/label.tsx @@ -3,7 +3,7 @@ */ import { VisuallyHidden } from '../visually-hidden'; import { Label as BaseLabel } from './styles/input-control-styles'; -import type { PolymorphicComponentProps } from '../ui/context'; +import type { WordPressComponentProps } from '../ui/context'; import type { InputControlLabelProps } from './types'; export default function Label( { @@ -11,7 +11,7 @@ export default function Label( { hideLabelFromVision, htmlFor, ...props -}: PolymorphicComponentProps< InputControlLabelProps, 'label', false > ) { +}: WordPressComponentProps< InputControlLabelProps, 'label', false > ) { if ( ! children ) return null; if ( hideLabelFromVision ) { diff --git a/packages/components/src/input-control/styles/input-control-styles.tsx b/packages/components/src/input-control/styles/input-control-styles.tsx index bbd518ccb4822d..c6471fa96ead55 100644 --- a/packages/components/src/input-control/styles/input-control-styles.tsx +++ b/packages/components/src/input-control/styles/input-control-styles.tsx @@ -9,7 +9,7 @@ import type { CSSProperties, ReactNode } from 'react'; /** * Internal dependencies */ -import type { PolymorphicComponentProps } from '../../ui/context'; +import type { WordPressComponentProps } from '../../ui/context'; import { Flex, FlexItem } from '../../flex'; import { Text } from '../../text'; import { COLORS, rtl } from '../../utils'; @@ -251,7 +251,7 @@ const BaseLabel = styled( Text )< { labelPosition?: LabelPosition } >` `; export const Label = ( - props: PolymorphicComponentProps< + props: WordPressComponentProps< { labelPosition?: LabelPosition; children: ReactNode }, 'label', false diff --git a/packages/components/src/input-control/types.ts b/packages/components/src/input-control/types.ts index e166ffd6219ba3..971075e56a5ee4 100644 --- a/packages/components/src/input-control/types.ts +++ b/packages/components/src/input-control/types.ts @@ -15,7 +15,7 @@ import type { useDrag } from 'react-use-gesture'; */ import type { StateReducer } from './reducer/state'; import type { FlexProps } from '../flex/types'; -import type { PolymorphicComponentProps } from '../ui/context'; +import type { WordPressComponentProps } from '../ui/context'; export type LabelPosition = 'top' | 'bottom' | 'side' | 'edge'; @@ -65,9 +65,9 @@ export interface InputBaseProps extends BaseProps, FlexProps { export interface InputControlProps extends Omit< InputBaseProps, 'children' | 'isFocused' >, /** - * The `prefix` prop in `PolymorphicComponentProps< InputFieldProps, 'input', false >` comes from the + * The `prefix` prop in `WordPressComponentProps< InputFieldProps, 'input', false >` comes from the * `HTMLInputAttributes` and clashes with the one from `InputBaseProps`. So we have to omit it from - * `PolymorphicComponentProps< InputFieldProps, 'input', false >` in order that `InputBaseProps[ 'prefix' ]` + * `WordPressComponentProps< InputFieldProps, 'input', false >` in order that `InputBaseProps[ 'prefix' ]` * be the only prefix prop. Otherwise it tries to do a union of the two prefix properties and you end up * with an unresolvable type. * @@ -75,7 +75,7 @@ export interface InputControlProps * for InputField are passed through. */ Omit< - PolymorphicComponentProps< InputFieldProps, 'input', false >, + WordPressComponentProps< InputFieldProps, 'input', false >, 'stateReducer' | 'prefix' | 'isFocused' | 'setIsFocused' > { __unstableStateReducer?: InputFieldProps[ 'stateReducer' ]; diff --git a/packages/components/src/item-group/item-group/component.tsx b/packages/components/src/item-group/item-group/component.tsx index 2b77ec041e055c..e58eae897658cb 100644 --- a/packages/components/src/item-group/item-group/component.tsx +++ b/packages/components/src/item-group/item-group/component.tsx @@ -7,14 +7,14 @@ import type { Ref } from 'react'; /** * Internal dependencies */ -import { contextConnect, PolymorphicComponentProps } from '../../ui/context'; +import { contextConnect, WordPressComponentProps } from '../../ui/context'; import { useItemGroup } from './hook'; import { ItemGroupContext, useItemGroupContext } from '../context'; import { View } from '../../view'; import type { ItemGroupProps } from '../types'; function ItemGroup( - props: PolymorphicComponentProps< ItemGroupProps, 'div' >, + props: WordPressComponentProps< ItemGroupProps, 'div' >, forwardedRef: Ref< any > ) { const { diff --git a/packages/components/src/item-group/item-group/hook.ts b/packages/components/src/item-group/item-group/hook.ts index 90bb63bae3959c..f8ec3740e721ca 100644 --- a/packages/components/src/item-group/item-group/hook.ts +++ b/packages/components/src/item-group/item-group/hook.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { useContextSystem, PolymorphicComponentProps } from '../../ui/context'; +import { useContextSystem, WordPressComponentProps } from '../../ui/context'; /** * Internal dependencies @@ -11,7 +11,7 @@ import { useCx } from '../../utils/hooks/use-cx'; import type { ItemGroupProps } from '../types'; export function useItemGroup( - props: PolymorphicComponentProps< ItemGroupProps, 'div' > + props: WordPressComponentProps< ItemGroupProps, 'div' > ) { const { className, diff --git a/packages/components/src/item-group/item/component.tsx b/packages/components/src/item-group/item/component.tsx index 6a0eac90a93c98..18f6ae0eb99534 100644 --- a/packages/components/src/item-group/item/component.tsx +++ b/packages/components/src/item-group/item/component.tsx @@ -9,11 +9,11 @@ import type { Ref } from 'react'; */ import type { ItemProps } from '../types'; import { useItem } from './hook'; -import { contextConnect, PolymorphicComponentProps } from '../../ui/context'; +import { contextConnect, WordPressComponentProps } from '../../ui/context'; import { View } from '../../view'; function Item( - props: PolymorphicComponentProps< ItemProps, 'div' >, + props: WordPressComponentProps< ItemProps, 'div' >, forwardedRef: Ref< any > ) { const { role, wrapperClassName, ...otherProps } = useItem( props ); diff --git a/packages/components/src/item-group/item/hook.ts b/packages/components/src/item-group/item/hook.ts index ac7ad6356ceb34..2ff7574751098e 100644 --- a/packages/components/src/item-group/item/hook.ts +++ b/packages/components/src/item-group/item/hook.ts @@ -7,15 +7,13 @@ import type { ElementType } from 'react'; /** * Internal dependencies */ -import { useContextSystem, PolymorphicComponentProps } from '../../ui/context'; +import { useContextSystem, WordPressComponentProps } from '../../ui/context'; import * as styles from '../styles'; import { useItemGroupContext } from '../context'; import { useCx } from '../../utils/hooks/use-cx'; import type { ItemProps } from '../types'; -export function useItem( - props: PolymorphicComponentProps< ItemProps, 'div' > -) { +export function useItem( props: WordPressComponentProps< ItemProps, 'div' > ) { const { isAction = false, as: asProp, diff --git a/packages/components/src/scrollable/hook.js b/packages/components/src/scrollable/hook.js index bcfab8ef7c9270..ce8b01e14724d3 100644 --- a/packages/components/src/scrollable/hook.js +++ b/packages/components/src/scrollable/hook.js @@ -12,7 +12,7 @@ import { useCx } from '../utils/hooks/use-cx'; /* eslint-disable jsdoc/valid-types */ /** - * @param {import('../ui/context').PolymorphicComponentProps<import('./types').Props, 'div'>} props + * @param {import('../ui/context').WordPressComponentProps<import('./types').Props, 'div'>} props */ /* eslint-enable jsdoc/valid-types */ export function useScrollable( props ) { diff --git a/packages/components/src/select-control/index.tsx b/packages/components/src/select-control/index.tsx index db0f7917c33d76..e3514f7b67f089 100644 --- a/packages/components/src/select-control/index.tsx +++ b/packages/components/src/select-control/index.tsx @@ -21,7 +21,7 @@ import InputBase from '../input-control/input-base'; import type { InputBaseProps, LabelPosition } from '../input-control/types'; import { Select, DownArrowWrapper } from './styles/select-control-styles'; import type { Size } from './types'; -import type { PolymorphicComponentProps } from '../ui/context'; +import type { WordPressComponentProps } from '../ui/context'; function useUniqueId( idProp?: string ) { const instanceId = useInstanceId( SelectControl ); @@ -71,7 +71,7 @@ function SelectControl( prefix, suffix, ...props - }: PolymorphicComponentProps< SelectControlProps, 'select', false >, + }: WordPressComponentProps< SelectControlProps, 'select', false >, ref: Ref< HTMLSelectElement > ) { const [ isFocused, setIsFocused ] = useState( false ); diff --git a/packages/components/src/spacer/hook.ts b/packages/components/src/spacer/hook.ts index dda1a7d40cf0ae..c833ec90d896ae 100644 --- a/packages/components/src/spacer/hook.ts +++ b/packages/components/src/spacer/hook.ts @@ -6,8 +6,7 @@ import { css } from '@emotion/react'; /** * Internal dependencies */ -import { useContextSystem } from '../ui/context'; -import type { PolymorphicComponentProps } from '../ui/context'; +import { useContextSystem, WordPressComponentProps } from '../ui/context'; import { space } from '../ui/utils/space'; import { useCx } from '../utils/hooks/use-cx'; import type { Props } from './types'; @@ -15,7 +14,7 @@ import type { Props } from './types'; const isDefined = < T >( o: T ): o is Exclude< T, null | undefined > => typeof o !== 'undefined' && o !== null; -export function useSpacer( props: PolymorphicComponentProps< Props, 'div' > ) { +export function useSpacer( props: WordPressComponentProps< Props, 'div' > ) { const { className, margin, diff --git a/packages/components/src/surface/hook.js b/packages/components/src/surface/hook.js index a25e6d118c6f30..65d5937a47b927 100644 --- a/packages/components/src/surface/hook.js +++ b/packages/components/src/surface/hook.js @@ -11,7 +11,7 @@ import * as styles from './styles'; import { useCx } from '../utils/hooks/use-cx'; /** - * @param {import('../ui/context').PolymorphicComponentProps<import('./types').Props, 'div'>} props + * @param {import('../ui/context').WordPressComponentProps<import('./types').Props, 'div'>} props */ export function useSurface( props ) { const { diff --git a/packages/components/src/text/hook.js b/packages/components/src/text/hook.js index 8c3bb3f31521c3..70ccee41f629af 100644 --- a/packages/components/src/text/hook.js +++ b/packages/components/src/text/hook.js @@ -23,7 +23,7 @@ import { getLineHeight } from './get-line-height'; import { useCx } from '../utils/hooks/use-cx'; /** - * @param {import('../ui/context').PolymorphicComponentProps<import('./types').Props, 'span'>} props + * @param {import('../ui/context').WordPressComponentProps<import('./types').Props, 'span'>} props */ export default function useText( props ) { const { diff --git a/packages/components/src/toggle-group-control/component.tsx b/packages/components/src/toggle-group-control/component.tsx index 2ab3df7d10b639..1f6758b8e0f7c8 100644 --- a/packages/components/src/toggle-group-control/component.tsx +++ b/packages/components/src/toggle-group-control/component.tsx @@ -18,7 +18,7 @@ import { useMergeRefs, useInstanceId } from '@wordpress/compose'; import { contextConnect, useContextSystem, - PolymorphicComponentProps, + WordPressComponentProps, } from '../ui/context'; import { View } from '../view'; import BaseControl from '../base-control'; @@ -31,7 +31,7 @@ import ToggleGroupControlContext from './toggle-group-control-context'; const noop = () => {}; function ToggleGroupControl( - props: PolymorphicComponentProps< ToggleGroupControlProps, 'input' >, + props: WordPressComponentProps< ToggleGroupControlProps, 'input' >, forwardedRef: import('react').Ref< any > ) { const { diff --git a/packages/components/src/toggle-group-control/toggle-group-control-option.tsx b/packages/components/src/toggle-group-control/toggle-group-control-option.tsx index 96b1f4692c7329..53bc37a6462ab7 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control-option.tsx +++ b/packages/components/src/toggle-group-control/toggle-group-control-option.tsx @@ -10,7 +10,7 @@ import { useInstanceId } from '@wordpress/compose'; import { contextConnect, useContextSystem, - PolymorphicComponentProps, + WordPressComponentProps, } from '../ui/context'; import ToggleGroupControlButton from './toggle-group-control-button'; import type { @@ -42,7 +42,7 @@ function getShowSeparator( } function ToggleGroupControlOption( - props: PolymorphicComponentProps< ToggleGroupControlOptionProps, 'input' >, + props: WordPressComponentProps< ToggleGroupControlOptionProps, 'input' >, forwardedRef: import('react').Ref< any > ) { const toggleGroupControlContext = useToggleGroupControlContext(); diff --git a/packages/components/src/truncate/hook.js b/packages/components/src/truncate/hook.js index cd9f313da0e95c..64e45c1bed5ea7 100644 --- a/packages/components/src/truncate/hook.js +++ b/packages/components/src/truncate/hook.js @@ -17,7 +17,7 @@ import { TRUNCATE_ELLIPSIS, TRUNCATE_TYPE, truncateContent } from './utils'; import { useCx } from '../utils/hooks/use-cx'; /** - * @param {import('../ui/context').PolymorphicComponentProps<import('./types').Props, 'span'>} props + * @param {import('../ui/context').WordPressComponentProps<import('./types').Props, 'span'>} props */ export default function useTruncate( props ) { const { diff --git a/packages/components/src/ui/color-picker/component.tsx b/packages/components/src/ui/color-picker/component.tsx index 5fa862a07ef9a7..977826c6ccf958 100644 --- a/packages/components/src/ui/color-picker/component.tsx +++ b/packages/components/src/ui/color-picker/component.tsx @@ -19,7 +19,7 @@ import { __ } from '@wordpress/i18n'; import { useContextSystem, contextConnect, - PolymorphicComponentProps, + WordPressComponentProps, } from '../context'; import { HStack } from '../../h-stack'; import Button from '../../button'; @@ -53,7 +53,7 @@ const getSafeColor = ( }; const ColorPicker = ( - props: PolymorphicComponentProps< ColorPickerProps, 'div', false >, + props: WordPressComponentProps< ColorPickerProps, 'div', false >, forwardedRef: Ref< any > ) => { const { diff --git a/packages/components/src/ui/context/context-connect.js b/packages/components/src/ui/context/context-connect.js index df11228b1e17ea..5dcbb174dbdd03 100644 --- a/packages/components/src/ui/context/context-connect.js +++ b/packages/components/src/ui/context/context-connect.js @@ -24,12 +24,12 @@ import { getStyledClassNameFromKey } from './get-styled-class-name-from-key'; * The hope is that we can improve render performance by removing functional * component wrappers. * - * @template {import('./polymorphic-component').PolymorphicComponentProps<{}, any, any>} P + * @template {import('./wordpress-component').WordPressComponentProps<{}, any, any>} P * @param {(props: P, ref: import('react').Ref<any>) => JSX.Element | null} Component The component to register into the Context system. * @param {string} namespace The namespace to register the component under. * @param {Object} options * @param {boolean} [options.memo=false] - * @return {import('./polymorphic-component').PolymorphicComponentFromProps<P>} The connected PolymorphicComponent + * @return {import('./wordpress-component').WordPressComponentFromProps<P>} The connected WordPressComponent */ export function contextConnect( Component, namespace, options = {} ) { /* eslint-enable jsdoc/valid-types */ @@ -65,7 +65,7 @@ export function contextConnect( Component, namespace, options = {} ) { // @ts-ignore internal property WrappedComponent[ CONNECT_STATIC_NAMESPACE ] = uniq( mergedNamespace ); - // @ts-ignore PolymorphicComponent property + // @ts-ignore WordPressComponent property WrappedComponent.selector = `.${ getStyledClassNameFromKey( namespace ) }`; // @ts-ignore diff --git a/packages/components/src/ui/context/index.js b/packages/components/src/ui/context/index.js index e19795d7b7a13b..54694e47304ff2 100644 --- a/packages/components/src/ui/context/index.js +++ b/packages/components/src/ui/context/index.js @@ -8,4 +8,4 @@ export { getConnectNamespace, } from './context-connect'; export { useContextSystem } from './use-context-system'; -export * from './polymorphic-component'; +export * from './wordpress-component'; diff --git a/packages/components/src/ui/context/polymorphic-component.ts b/packages/components/src/ui/context/wordpress-component.ts similarity index 79% rename from packages/components/src/ui/context/polymorphic-component.ts rename to packages/components/src/ui/context/wordpress-component.ts index fae6cc41418db8..2bf4372ec6b6e2 100644 --- a/packages/components/src/ui/context/polymorphic-component.ts +++ b/packages/components/src/ui/context/wordpress-component.ts @@ -11,7 +11,7 @@ import type * as React from 'react'; * by `ComponentPropsWithRef`. The context is that components should require the `children` * prop explicitely when needed (see https://github.com/WordPress/gutenberg/pull/31817). */ -export type PolymorphicComponentProps< +export type WordPressComponentProps< P, T extends React.ElementType, IsPolymorphic extends boolean = true @@ -23,17 +23,17 @@ export type PolymorphicComponentProps< } : { as?: never } ); -export type PolymorphicComponent< +export type WordPressComponent< T extends React.ElementType, O, IsPolymorphic extends boolean > = { < TT extends React.ElementType >( - props: PolymorphicComponentProps< O, TT, IsPolymorphic > & + props: WordPressComponentProps< O, TT, IsPolymorphic > & ( IsPolymorphic extends true ? { as: TT } : { as: never } ) ): JSX.Element | null; ( - props: PolymorphicComponentProps< O, T, IsPolymorphic > + props: WordPressComponentProps< O, T, IsPolymorphic > ): JSX.Element | null; displayName?: string; /** @@ -47,8 +47,8 @@ export type PolymorphicComponent< selector: `.${ string }`; }; -export type PolymorphicComponentFromProps< +export type WordPressComponentFromProps< Props -> = Props extends PolymorphicComponentProps< infer P, infer T, infer I > - ? PolymorphicComponent< T, P, I > +> = Props extends WordPressComponentProps< infer P, infer T, infer I > + ? WordPressComponent< T, P, I > : never; diff --git a/packages/components/src/ui/control-group/component.js b/packages/components/src/ui/control-group/component.js index b1b82c581cdfdd..d291d436c11d4b 100644 --- a/packages/components/src/ui/control-group/component.js +++ b/packages/components/src/ui/control-group/component.js @@ -11,8 +11,8 @@ import { useControlGroup } from './hook'; import { contextConnect } from '../context'; /** - * @param {import('../context').PolymorphicComponentProps<import('./types').Props, 'div'>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../context').WordPressComponentProps<import('./types').Props, 'div'>} props + * @param {import('react').Ref<any>} forwardedRef */ function ControlGroup( props, forwardedRef ) { const { diff --git a/packages/components/src/ui/control-group/hook.js b/packages/components/src/ui/control-group/hook.js index a30b745f94b9a8..bc1f5d34c08b37 100644 --- a/packages/components/src/ui/control-group/hook.js +++ b/packages/components/src/ui/control-group/hook.js @@ -8,7 +8,7 @@ import * as styles from './styles'; import { useCx } from '../../utils/hooks/use-cx'; /** - * @param {import('../context').PolymorphicComponentProps<import('./types').Props, 'div'>} props + * @param {import('../context').WordPressComponentProps<import('./types').Props, 'div'>} props */ export function useControlGroup( props ) { const { diff --git a/packages/components/src/ui/control-label/hook.js b/packages/components/src/ui/control-label/hook.js index e376cc18f726ca..c7f10444fab792 100644 --- a/packages/components/src/ui/control-label/hook.js +++ b/packages/components/src/ui/control-label/hook.js @@ -8,7 +8,7 @@ import * as styles from './styles'; import { useCx } from '../../utils/hooks/use-cx'; /** - * @param {import('../context').PolymorphicComponentProps<import('./types').Props, 'label'>} props + * @param {import('../context').WordPressComponentProps<import('./types').Props, 'label'>} props */ export function useControlLabel( props ) { const { diff --git a/packages/components/src/ui/form-group/form-group-content.js b/packages/components/src/ui/form-group/form-group-content.js index a84f5fa32faab4..21b95ff9068233 100644 --- a/packages/components/src/ui/form-group/form-group-content.js +++ b/packages/components/src/ui/form-group/form-group-content.js @@ -12,7 +12,7 @@ import FormGroupHelp from './form-group-help'; import FormGroupLabel from './form-group-label'; /** - * @param {import('../context').PolymorphicComponentProps<import('./types').FormGroupContentProps, 'label'>} props + * @param {import('../context').WordPressComponentProps<import('./types').FormGroupContentProps, 'label'>} props */ function FormGroupContent( { alignLabel, diff --git a/packages/components/src/ui/form-group/form-group-label.js b/packages/components/src/ui/form-group/form-group-label.js index 8d6c282018cad8..01e9f9be3c51e9 100644 --- a/packages/components/src/ui/form-group/form-group-label.js +++ b/packages/components/src/ui/form-group/form-group-label.js @@ -10,7 +10,7 @@ import { ControlLabel } from '../control-label'; import { VisuallyHidden } from '../../visually-hidden'; /** - * @param {import('../context').PolymorphicComponentProps<import('./types').FormGroupLabelProps, 'label'>} props + * @param {import('../context').WordPressComponentProps<import('./types').FormGroupLabelProps, 'label'>} props * @return {JSX.Element | null} The form group's label. */ function FormGroupLabel( { children, id, labelHidden = false, ...props } ) { diff --git a/packages/components/src/ui/form-group/form-group.js b/packages/components/src/ui/form-group/form-group.js index 9885aa63d24471..fc9eae01325f47 100644 --- a/packages/components/src/ui/form-group/form-group.js +++ b/packages/components/src/ui/form-group/form-group.js @@ -8,8 +8,8 @@ import FormGroupContent from './form-group-content'; import { useFormGroup } from './use-form-group'; /** - * @param {import('../context').PolymorphicComponentProps<import('./types').FormGroupProps, 'div'>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../context').WordPressComponentProps<import('./types').FormGroupProps, 'div'>} props + * @param {import('react').Ref<any>} forwardedRef */ function FormGroup( props, forwardedRef ) { const { contentProps, horizontal, ...otherProps } = useFormGroup( props ); diff --git a/packages/components/src/ui/form-group/use-form-group.js b/packages/components/src/ui/form-group/use-form-group.js index 3b73fc69f5f4ef..eebc5ae3506398 100644 --- a/packages/components/src/ui/form-group/use-form-group.js +++ b/packages/components/src/ui/form-group/use-form-group.js @@ -11,7 +11,7 @@ import * as styles from './form-group-styles'; import { useCx } from '../../utils/hooks/use-cx'; /** - * @param {import('../context').PolymorphicComponentProps<import('./types').FormGroupProps, 'div'>} props + * @param {import('../context').WordPressComponentProps<import('./types').FormGroupProps, 'div'>} props */ export function useFormGroup( props ) { const { diff --git a/packages/components/src/ui/shortcut/component.tsx b/packages/components/src/ui/shortcut/component.tsx index 911aced391c148..f5bbb6a5d791ee 100644 --- a/packages/components/src/ui/shortcut/component.tsx +++ b/packages/components/src/ui/shortcut/component.tsx @@ -7,8 +7,11 @@ import type { Ref } from 'react'; /** * Internal dependencies */ -import { useContextSystem, contextConnect } from '../context'; -import type { PolymorphicComponentProps } from '../context'; +import { + useContextSystem, + contextConnect, + WordPressComponentProps, +} from '../context'; import { View } from '../../view'; export interface ShortcutDescription { @@ -22,7 +25,7 @@ export interface Props { } function Shortcut( - props: PolymorphicComponentProps< Props, 'span' >, + props: WordPressComponentProps< Props, 'span' >, forwardedRef: Ref< any > ): JSX.Element | null { const { diff --git a/packages/components/src/ui/spinner/component.js b/packages/components/src/ui/spinner/component.js index 12cb30f0d712b8..c5b45fccf0b8af 100644 --- a/packages/components/src/ui/spinner/component.js +++ b/packages/components/src/ui/spinner/component.js @@ -16,8 +16,8 @@ import { COLORS } from '../../utils/colors-values'; /** * - * @param {import('../context').PolymorphicComponentProps<Props, 'div'>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../context').WordPressComponentProps<Props, 'div'>} props + * @param {import('react').Ref<any>} forwardedRef */ function Spinner( props, forwardedRef ) { const { diff --git a/packages/components/src/ui/tooltip/component.js b/packages/components/src/ui/tooltip/component.js index f618f4633d48f8..78ab9132407e5e 100644 --- a/packages/components/src/ui/tooltip/component.js +++ b/packages/components/src/ui/tooltip/component.js @@ -18,8 +18,8 @@ import TooltipContent from './content'; import { TooltipShortcut } from './styles'; /** - * @param {import('../context').PolymorphicComponentProps<import('./types').Props, 'div'>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../context').WordPressComponentProps<import('./types').Props, 'div'>} props + * @param {import('react').Ref<any>} forwardedRef */ function Tooltip( props, forwardedRef ) { const { diff --git a/packages/components/src/ui/tooltip/content.js b/packages/components/src/ui/tooltip/content.js index 08c51288dae23f..b2d533f8288c0b 100644 --- a/packages/components/src/ui/tooltip/content.js +++ b/packages/components/src/ui/tooltip/content.js @@ -17,8 +17,8 @@ const { TooltipPopoverView } = styles; /** * - * @param {import('../context').PolymorphicComponentProps<import('./types').ContentProps, 'div'>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../context').WordPressComponentProps<import('./types').ContentProps, 'div'>} props + * @param {import('react').Ref<any>} forwardedRef */ function TooltipContent( props, forwardedRef ) { const { children, className, ...otherProps } = useContextSystem( diff --git a/packages/components/src/ui/utils/create-component.tsx b/packages/components/src/ui/utils/create-component.tsx index f9bd8e6d7d3ed4..8e451cffef11c9 100644 --- a/packages/components/src/ui/utils/create-component.tsx +++ b/packages/components/src/ui/utils/create-component.tsx @@ -10,14 +10,14 @@ import type { As } from 'reakit-utils/types'; */ import { contextConnect } from '../context'; import type { - PolymorphicComponentProps, - PolymorphicComponentFromProps, + WordPressComponentProps, + WordPressComponentFromProps, } from '../context'; import { View } from '../../view'; interface Options< A extends As, - P extends PolymorphicComponentProps< {}, A, any > + P extends WordPressComponentProps< {}, A, any > > { as: A; name: string; @@ -37,13 +37,13 @@ interface Options< */ export const createComponent = < A extends As, - P extends PolymorphicComponentProps< {}, A, any > + P extends WordPressComponentProps< {}, A, any > >( { as, name, useHook, memo = false, -}: Options< A, P > ): PolymorphicComponentFromProps< P > => { +}: Options< A, P > ): WordPressComponentFromProps< P > => { function Component( props: P, forwardedRef: Ref< any > ) { const otherProps = useHook( props ); diff --git a/packages/components/src/ui/utils/test/create-component.js b/packages/components/src/ui/utils/test/create-component.js index 01dbd7b9917974..970a774badf29a 100644 --- a/packages/components/src/ui/utils/test/create-component.js +++ b/packages/components/src/ui/utils/test/create-component.js @@ -15,7 +15,7 @@ import { createComponent } from '../create-component'; describe( 'createComponent', () => { /** - * @param {import('../context').PolymorphicComponentProps<{}, 'output'>} props + * @param {import('../context').WordPressComponentProps<{}, 'output'>} props */ const useHook = ( props ) => ( { ...props, 'data-hook-test-prop': true } ); const name = 'Output'; diff --git a/packages/components/src/v-stack/hook.js b/packages/components/src/v-stack/hook.js index 0dcd4d33c739ae..1852da47bad090 100644 --- a/packages/components/src/v-stack/hook.js +++ b/packages/components/src/v-stack/hook.js @@ -6,7 +6,7 @@ import { useHStack } from '../h-stack'; /** * - * @param {import('../ui/context').PolymorphicComponentProps<import('./types').Props, 'div'>} props + * @param {import('../ui/context').WordPressComponentProps<import('./types').Props, 'div'>} props */ export function useVStack( props ) { const { expanded = false, ...otherProps } = useContextSystem( diff --git a/packages/components/src/view/component.js b/packages/components/src/view/component.js index 095576a79e886f..4d7808807bd1f3 100644 --- a/packages/components/src/view/component.js +++ b/packages/components/src/view/component.js @@ -20,7 +20,7 @@ import styled from '@emotion/styled'; * } * ``` * - * @type {import('../ui/context').PolymorphicComponent<'div', { children?: import('react').ReactNode }, true>} + * @type {import('../ui/context').WordPressComponent<'div', { children?: import('react').ReactNode }, true>} */ // @ts-ignore const View = styled.div``; diff --git a/packages/components/src/visually-hidden/component.js b/packages/components/src/visually-hidden/component.js index 01b9e65ddc5119..ba0096d3a8bd85 100644 --- a/packages/components/src/visually-hidden/component.js +++ b/packages/components/src/visually-hidden/component.js @@ -6,8 +6,8 @@ import { visuallyHidden } from './styles'; import { View } from '../view'; /** - * @param {import('../ui/context').PolymorphicComponentProps<{ children: import('react').ReactNode }, 'div'>} props - * @param {import('react').Ref<any>} forwardedRef + * @param {import('../ui/context').WordPressComponentProps<{ children: import('react').ReactNode }, 'div'>} props + * @param {import('react').Ref<any>} forwardedRef */ function VisuallyHidden( props, forwardedRef ) { const { style: styleProp, ...contextProps } = useContextSystem( diff --git a/packages/components/src/z-stack/component.tsx b/packages/components/src/z-stack/component.tsx index 32767f2877843d..b6ee9fad3f1dcc 100644 --- a/packages/components/src/z-stack/component.tsx +++ b/packages/components/src/z-stack/component.tsx @@ -14,7 +14,7 @@ import { isValidElement } from '@wordpress/element'; */ import { getValidChildren } from '../ui/utils/get-valid-children'; import { contextConnect, useContextSystem } from '../ui/context'; -import type { PolymorphicComponentProps } from '../ui/context'; +import type { WordPressComponentProps } from '../ui/context'; import { ZStackView, ZStackChildView } from './styles'; export interface ZStackProps { @@ -43,7 +43,7 @@ export interface ZStackProps { } function ZStack( - props: PolymorphicComponentProps< ZStackProps, 'div' >, + props: WordPressComponentProps< ZStackProps, 'div' >, forwardedRef: Ref< any > ) { const { From 5ae8047326755d43d5b857f8863f03094bcc7cec Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Thu, 2 Sep 2021 10:42:47 +0200 Subject: [PATCH 113/214] [RNMobile][Embed block] Enable embed preview for a list of providers (#34446) * Enable embed preview by static list of providers * Enable embed inline previews on production * Update react-native-editor- changelog * Update react-native-editor changelog --- .../block-library/src/embed/edit.native.js | 11 ++++++++- .../src/embed/embed-preview.native.js | 23 ++++++++----------- packages/react-native-editor/CHANGELOG.md | 2 +- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/block-library/src/embed/edit.native.js b/packages/block-library/src/embed/edit.native.js index c0a0946a1fa86d..deb4fe67e76032 100644 --- a/packages/block-library/src/embed/edit.native.js +++ b/packages/block-library/src/embed/edit.native.js @@ -32,6 +32,10 @@ import { import { store as coreStore } from '@wordpress/core-data'; import { View } from '@wordpress/primitives'; +// The inline preview feature will be released progressible, for this reason +// the embed will only be considered previewable for the following providers list. +const PREVIEWABLE_PROVIDERS = [ 'youtube', 'twitter' ]; + const EmbedEdit = ( props ) => { const { attributes: { align, providerNameSlug, previewable, responsive, url }, @@ -243,7 +247,12 @@ const EmbedEdit = ( props ) => { label={ title } onFocus={ onFocus } preview={ preview } - previewable={ previewable } + previewable={ + previewable && + PREVIEWABLE_PROVIDERS.includes( + providerNameSlug + ) + } type={ type } url={ url } /> diff --git a/packages/block-library/src/embed/embed-preview.native.js b/packages/block-library/src/embed/embed-preview.native.js index 7bea26775b6341..8670b3c2d22668 100644 --- a/packages/block-library/src/embed/embed-preview.native.js +++ b/packages/block-library/src/embed/embed-preview.native.js @@ -123,19 +123,16 @@ const EmbedPreview = ( { disabled={ ! isSelected } > <View> - { - // eslint-disable-next-line no-undef - __DEV__ && previewable ? ( - embedWrapper - ) : ( - <EmbedNoPreview - label={ label } - icon={ icon } - isSelected={ isSelected } - onPress={ () => setIsCaptionSelected( false ) } - /> - ) - } + { previewable ? ( + embedWrapper + ) : ( + <EmbedNoPreview + label={ label } + icon={ icon } + isSelected={ isSelected } + onPress={ () => setIsCaptionSelected( false ) } + /> + ) } <BlockCaption accessibilityLabelCreator={ accessibilityLabelCreator } accessible diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 663f41c835aede..0d8aec88a78d59 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -10,7 +10,7 @@ For each user feature we should also add a importance categorization label to i --> ## Unreleased - +- [**] Enable embed preview for a list of providers (for now only YouTube and Twitter) [#34446] ## 1.60.1 - [*] RNmobile: Fix the cancel button on Block Variation Picker / Columns Block. [#34249] - [*] Column block: Fix Android close button alignment. [#34332] From 4fc1b6f1b16a032d4e732519074973cb3292f757 Mon Sep 17 00:00:00 2001 From: Chip <chip.snyder3@gmail.com> Date: Thu, 2 Sep 2021 05:35:07 -0400 Subject: [PATCH 114/214] [Mobile] Pass the Gallery v2 Flag over from the editor (#33314) * Refactor gallery to nested image blocks * Fix issue with images not loading on first selection from media gallery * Remove the default columns setting as we don't have access to innerBlocks at the point that the block validation is run * Revert "Remove the default columns setting as we don't have access to innerBlocks at the point that the block validation is run" This reverts commit 4d95e95d50ae8410868a6d7aca6b9f4c4536d36b. * Add image count so we can work out default columns as innerBlocks not available at point of block validation * Disable the innerBlocks dropzones so drag drop works same as existing gallery - with the idea that we will improve the innerblocks drag and drop in a later PR * Lint changes * Revert "Lint changes" This reverts commit 9b0abccdc4dc706011e315457a95418f5325f5c1. * Revert "Disable the innerBlocks dropzones so drag drop works same as existing gallery - with the idea that we will improve the innerblocks drag and drop in a later PR" This reverts commit cc05d60627d4c4970afef73306970762f39c4785. * Suggested solution for handling multiple file drop into gallery * Remove non image files from drag and drop and disable individual image drop zone * Fix transform to individual images * Fix transform from individual images * Revert drag and drop transform changes * Add gallery transform to image block to override the default gallery transform when dragging multiple images onto the gallery * Move handling of file uploads to Gallery from media placeholder * split innerblocks mapping into separate effect to reduce chatter * Add useMemo to currentImageOptions * reuse existing innerBlocks rather than recreating with every new image selection * Switch to useMemo for updating local image const instead of local component state * Fix issue with image sizing not being available on initial load of component some times * Memoise the useImageSizes hook * Fix issue with media browser defaulting to edit gallery view * Fix missed incorrect use of addToGallery * Add some extra effects for getting the imageData as the getMedia call is async so need to keep circling through the innerblocks updates until we have all the data we need * Simplify the imageData by using a useSelect * Another optimisation - only return a new imageData reference if all images have data resolved * Refactored Gallery: Add loading state to gallery image size options (#27087) * Add loading spinner for image size options Co-authored-by: Glen Davies <glen.davies@a8c.com> * Initial deprecations commit * Fix issue with linkDestination not being applied in migration * Refactor gallery deprecations * Fix missing attributes from migration * Update deprecation to set allowResize The imageEdit component defaults the context value for allowResize to true. The refactored gallery sets this to false by default. Setting allowResize to false when migrating a deprecated gallery allows images to be cropped in the display the same as the gallery when the post is saved and gallery reloaded. * Fix issue with crop not working when certain plugins are loaded * Fix SCSS lint errors * Update the block example * Linting fixes * Fix the e2e test and the accessibility issue with having aria group role on a list item * Fix the e2e test and the accessibility issue with having aria group role on a list item * Fix frontend omission of wp-block-image class Also tweaks the gallery styles to remove the margins the use of `wp-block-image` introduces when it is moved to the inner `figure` element. * change deprecation to use imageCount as isEligible check as it seems that some previous block versions may not have an ids attribute * Move back to a single deprecations file and reorder in array * Remove additional check in v5 isEligible * Fix the v4 migration * Fix styles for Safari compatibility * Remove unnecessary gallery editor styles * Fix typo in deprecations * Restore styles to render deprecated gallery versions * Avoid applying flex styles to IE11 * Add additional selector to prevent the hidden individual image drop zones ending up display flex instead of hidden * IE11 styling improvements * Apply default style class to new images added to gallery * fix linting issues * Move block props to the outer wrapper * Revert "Move block props to the outer wrapper" This reverts commit e0723a7068cd56cb83bb80ded67444d7676aa4f8. * Revert "Revert "Move block props to the outer wrapper"" This reverts commit a7504fd8386efd46577e2d9a566e7b816404e090. * Fetch media if isListItem is true * Change context from isListItem to isGrouped * Remove wrapper div * remove extra wrapper around media placeholder and caption and use flex css instead * Revert "remove extra wrapper around media placeholder and caption and use flex css instead * Revert "Remove external div wrapper by moving InnerBlocks to a fragment" * another update to image wrapper * put media uploader outside figure so structure matches front end * Replace div with View for the sake of native code * Move setting of attributes to the child images * Gallery Block Refactor: Account for null image ids in gallery migrations (#27855) Co-authored-by: Glen Davies <glen.davies@a8c.com> * Remove the gradient and put caption under image if is-rounded style applied (#27869) * Add alignment fixes for non cropped images Co-authored-by: Glen Davies <glen.davies@a8c.com> * Remove outer div wrapper * Keep image margins while dragging sibling * Fix e2e test expected markup to match new structure * Gallery Block Refactor: Add handling of short code transforms * Removed unused prop * Account for undefined block and innerblocks in conversion to reusable block lifecycle * Add custom gutter sizes to refactored gallery (#28377) * Adjust editor styles to match new dom structure * Remove redundant styles that are duplicated in nested image blocks * Fix issue with Image block dragged out of Gallery still having inheritedAttributes set * When dragging an image block into a gallery make sure we don't wipe any custom links * fix issue with variable declaration order * Fix bug with custom link being overwritten by gallery linkTo changes * Fix application of gutter size CSS var (#28759) * Fix mobile width for gallery images * Add missing dependency to inner images selector * remove conversion to cover block if image in gallery * Add fallback to old gallery edit and save for existing gallery (#28961) Co-authored-by: Glen Davies <glen.davies@a8c.com> * Remove duplicate import * Remove need for temporary imageUploads attribute as we can just create the innerBlocks as part of transform * Remove handling of gallery attribute updates from child images * Move updating of attributes back to gallery and show snackbar to indicated to user that change was made to all images in the gallery * Update transforms to work with both versions of gallery * Remove redundant changes * Remove redundant changes * Remove unused method * Merge similar Image transforms. * Fix some issues missed when moving attribute setting back to gallery. * Simplify handling of images from media placeholder now that drag and drop files handled by transform * Fix typo * Fix broken upload from media placeholder * Fix issue with new file uploads overwriting existing blocks * Remove isGrouped dependency from call to get image as not needed now attributes handled by gallery * Remove custom gutter size feature Reasoning for removal can be found at https://github.com/WordPress/gutenberg/pull/25940#discussion_r574299055 * Use getMediaItems instead of getMedia for getting image data for all gallery images * Add back missing context prop from rebase conflict * Make use of new uniqueByBlock flag on updateBlockAttributes to batch child image updates and allow single undo to revert * Move the setting of the default attributes on new blocks to a single useEffect (#29328) Co-authored-by: Glen Davies <glen.davies@a8c.com> * Add snackbar notice ids (#29364) * Account for people adding and removing images from media browser so number of images the same, but list of images different to current. * Changes from PR feedback * Fix issue with deprecated constants * Fix linting and e2e test failures * Some more e2e fixes * Add transform from old gallery to new format * Memoize the allowedBlocks This allows the shallow equals check in the inner blocks' `useNestedSettingsUpdate` hook to pass successfully preventing the state update loop. * Add warning about image formats required if uploading to gallery * Move allowedBlocks outside of component to avoid useMemo use * Re-apply uncropped alignment changes lost in rebase * Fix issue with non-cropped images in deprecated gallery * Don't show media library buttons while images are still uploading as the media library needs the image ids to reconcile which images are already in the gallery * Remove gallerRef that was no longer doing anything * Respect sort order from Media Library (#30070) Co-authored-by: Glen Davies <glen.davies@a8c.com> * Gallery block refactor: make invalid file type errors consistent (#30396) Co-authored-by: Glen Davies <glen.davies@a8c.com> Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> * Fix issue with invalid type message when adding files via media browser 'Upload' button * Gallery block refactor: check for new images by clientId instead of id to stop link settings being lost when images edited (#30550) Co-authored-by: Glen Davies <glen.davies@a8c.com> * Apply changes from rebase to deprecated gallery * Apply patch from rebase to not select all items in media library when existing images have no ids * Copy caption across from image selected from media library (#30784) Co-authored-by: Glen Davies <glen.davies@a8c.com> * Add data-id to image to help with compatibility of refactored gallery with existing themes and plugins (#30710) Co-authored-by: Glen Davies <glen.davies@a8c.com> * Revert "Add data-id to image to help with compatibility of refactored gallery with existing themes and plugins (#30710)" This reverts commit a2253ec518f3769b280c7c7c27e8ea1d0f265a3f. * fix bug with image style being lost when gallery grouped (#31068) Co-authored-by: Glen Davies <glen.davies@a8c.com> * Only add RichText component if the figcaption is clicked to prevent it stealing focus every time block is selected (#31216) Co-authored-by: Glen Davies <glen.davies@a8c.com> * Move native v1 Gallery components to v1 directory * Use v1 defaultColumnsNumber in native v1 gallery * Fix bug with alt text not being copied from media library (#31066) Co-authored-by: Glen Davies <glen.davies@a8c.com> * Make onFocus optional in MediaPlaceholder * Add useInnerBlocksProps hook * Enable __experimentalGalleryRefactor flag under __DEV__ This is only temporary, for testing purposes. This will be replaced with an actual implementation (which will need to access the flag remotely). * Add WIP v2 gallery * Add numColumns to block-list flat list * Temporarily comment out spinner for pending imagesize option This will need to be addressed (since we can't have unwrapped text on mobile). For now, I'm commenting this out to proceed with cherry-picking the changes from the original mobile refactor branch. * Fix spacing * Adjust styles to avoid appender overlap * Add gallery caption * Fix lint Some of these "fixes" are simply disabling lint for the offending lines. These currently unused variables may be used in a later PR, so I'm leaving them in, for now, to help simplify reconciling the changes from the former refactor PR. * Fix sass lint * [Mobile] - Refactor gallery - cherry pick image edit native (#31826) * WIP-commit bring image changes from final state of original mobile PR This has unresolved / unmarked conflicts which will be resolve in subsequent commits. I am separating the commit to make the resolution process more transparent. * Remove duplicate / conflicting methods from performance refactor * Use context for imageCrop and isGrouped instead of isGallery * Remove non-existent inheritedAttributes attribute * Remove dead code from non-existent context attributes * Remove unused attributes prop from link settings * Cherry-pick BlockListItem changes Note: Since render was changed to renderContent, we should return early from render too, when blockWidth is falsey. * Return early from render instead of renderContent * Cherry-pick plumb blockProps through BlockListBlock I'm not sure yet whether it still makes sense to use blockProps in this way. I'm going to cherry-pick the commits by file, and sort out the need for this mechanism afterwards. * Cherry-pick add GridItem Since this is duplicated from the original mobile gallery code (Tiles component), it might make sense to export it for re-use. Previously, it was only moved, but now that we will maintain both versions, it has become a duplicate implementation. I will defer this to a later commit. * Cherry-pick BlockList Similar to blockProps mentioned earlier, gridProperties will be evaluated after cherry-picking the relevant changes, to see if there is another mechanism that may be more appropriate. * Cherry-pick StylePreview key change * Cherry-pick blockProps and gridProperties in InnerBlocks * Use sass var for galleryAppender padding Note: This also re-adds fullWidth style, which is still being used in both v1 and v2 mobile implementations. If this is superceded by a recent refactor of the block width styles, it may be worth revisiting this and removing / changing the implementation. * Cherry-pick remaining gallery changes Note: as before, blockProps and gridProperties should be re-evaluated in subsequent commits, if necessary. E.g. `imageCrop` is already recieved via context, and `isGroup` may be sufficient, eliminating the need for `isGallery`. * Remove numColumns Going back over the older commits, it seems there were a two strategies used to render the gallery images as a grid. One used the numColumns (same as used in the Columns block), and the other using the Grid component. This commit cleans up the unused parts of the former approach. * Remove blockProps This is no longer necessary, since we are using context to pass gallery-level attributes to the image blocks' rendering. * Fix gallery block.json (delete extra comma) * Remove unused imageCrop * Gallery refactor - Infer version from existing content (#32270) * Give content precedence over flag in edit wrapper This modifies the logic in the gallery edit wrapper function to infer version information from existing content when the editor encounters content created from the new implementation and the flag is not set. * Resolve merge conflicts This brings the gallery edit changes to the v1 directory, and merges the changes for the image block. * Use non-deprecated hook when possible This brings the same change from this PR: https://github.com/WordPress/gutenberg/pull/31027 which may have missed the deprecated hook in the refactor PR, since it hadn't landed yet. * Resolve conflict in block editor default settings This was an update for a new jsdoc linting rule. * Add back blockWidth contentContainerStyles in block list I believe this was inadvertantly removed in some earlier commits, so this commit adds it back. * Use boolean flags for variants in Platform module These flags allow for a slightly more flexible, performant, and terse way of branching by platform. For more details, see: https://github.com/WordPress/gutenberg/pull/18058#issuecomment-586118076 * Use boolean Platform flags * Only render imageSizeOptions loading spinner on web * Add default for destructured context in image edit This is necessary for unit tests, because they instantiate the block's edit component directly, and so the default context is not provided. * Temporarily hard-code experimenal gallery refactor flag to true This will be reverted once the block settings are fetched from the REST API. This is enabled for now for testing purposes. * Revert "Temporarily hard-code experimenal gallery refactor flag to true" This reverts commit 6fbaed9ec11de7b3a4bb511da561f7e8d4a76cad. * Update experiments page with warning about the mobile app version * Resolve conflict in image.js The conflict was from #33095 * Pass the Gallery v2 Flag over from the editor * Capture Gallery Refactor in initial props * Lint Fix * Minor changes from code review: * Reward experiments description * Add documentation about new media placeholder handleUpload flag * Add additional explanation of v1-6 deprecations * Rename the edit wrapper components to make useage clearer * Temporarily remove tranform from v1 gallery to v2 gallery * Improve naming of save and deprecated save methods * Rename __experimentalGalleryRefactor flag to __unstableGalleryWithInnerBlocks * Remove the isGrouped context as no longer needed. * Rename __unstableGalleryWithInnerBlocks to _unstableGalleryWithImageBlocks * Gallery block refactor: remove the imageCount attribute (#33677) * Remove the imageCount attribute and use CSS instead to set default columns Co-authored-by: Glen Davies <glen.davies@a8c.com> Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> * Fix broken scss * Fix php linting error * Changes new gallery flag name * Updates mobile warning * Removes the imageCount attribute * Remove the isGrouped context * Fixes lint issue * [Mobile] Renames Gallery v2 Flag to __unstableGalleryWithImageBlocks (#33816) * Renames __experimentalGalleryRefactor to __unstableGalleryWithImageBlocks * Fixes lint formatting issue * Fixes lint issue * Merge mobile refactor of gallery to nested image blocks into desktop refactor PR(#31306) * Move native v1 Gallery components to v1 directory * Use v1 defaultColumnsNumber in native v1 gallery * Make onFocus optional in MediaPlaceholder * Add useInnerBlocksProps hook * Enable __experimentalGalleryRefactor flag under __DEV__ * Add numColumns to block-list flat list * Fix spacing * Adjust styles to avoid appender overlap * Add gallery caption * Fix lint Some of these "fixes" are simply disabling lint for the offending lines. These currently unused variables may be used in a later PR, so I'm leaving them in, for now, to help simplify reconciling the changes from the former refactor PR. * Fix sass lint * [Mobile] - Refactor gallery - cherry pick image edit native (#31826) * Remove duplicate / conflicting methods from performance refactor * Use context for imageCrop and isGrouped instead of isGallery * Remove non-existent inheritedAttributes attribute * Remove dead code from non-existent context attributes * Remove unused attributes prop from link settings * Cherry-pick BlockListItem changes Note: Since render was changed to renderContent, we should return early from render too, when blockWidth is falsey. * Return early from render instead of renderContent * Cherry-pick plumb blockProps through BlockListBlock I'm not sure yet whether it still makes sense to use blockProps in this way. I'm going to cherry-pick the commits by file, and sort out the need for this mechanism afterwards. * Cherry-pick add GridItem Since this is duplicated from the original mobile gallery code (Tiles component), it might make sense to export it for re-use. Previously, it was only moved, but now that we will maintain both versions, it has become a duplicate implementation. I will defer this to a later commit. * Cherry-pick BlockList Similar to blockProps mentioned earlier, gridProperties will be evaluated after cherry-picking the relevant changes, to see if there is another mechanism that may be more appropriate. * Cherry-pick StylePreview key change * Cherry-pick blockProps and gridProperties in InnerBlocks * Use sass var for galleryAppender padding Note: This also re-adds fullWidth style, which is still being used in both v1 and v2 mobile implementations. If this is superceded by a recent refactor of the block width styles, it may be worth revisiting this and removing / changing the implementation. * Cherry-pick remaining gallery changes Note: as before, blockProps and gridProperties should be re-evaluated in subsequent commits, if necessary. E.g. `imageCrop` is already recieved via context, and `isGroup` may be sufficient, eliminating the need for `isGallery`. * Remove numColumns Going back over the older commits, it seems there were a two strategies used to render the gallery images as a grid. One used the numColumns (same as used in the Columns block), and the other using the Grid component. This commit cleans up the unused parts of the former approach. * Remove blockProps This is no longer necessary, since we are using context to pass gallery-level attributes to the image blocks' rendering. * Fix gallery block.json (delete extra comma) * Remove unused imageCrop * Add back blockWidth contentContainerStyles in block list * Use boolean flags for variants in Platform module These flags allow for a slightly more flexible, performant, and terse way of branching by platform. For more details, see: https://github.com/WordPress/gutenberg/pull/18058#issuecomment-586118076 * Use boolean Platform flags * Only render imageSizeOptions loading spinner on web * Add default for destructured context in image edit This is necessary for unit tests, because they instantiate the block's edit component directly, and so the default context is not provided. * Temporarily hard-code experimenal gallery refactor flag to true This will be reverted once the block settings are fetched from the REST API. This is enabled for now for testing purposes. * Update experiments page with warning about the mobile app version * Changes new gallery flag name * Updates mobile warning * Removes the imageCount attribute * Remove the isGrouped context * Fixes lint issue Co-authored-by: Antonis Lilis <antonis.lilis@automattic.com> * [Mobile] Android: Pass the Gallery v2 Flag over from the editor (#33544) Pass the Gallery v2 Flag over from the editor on Android Co-authored-by: Glen Davies <glen.davies@a8c.com> Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Co-authored-by: Glen Davies <glendaviesnz@users.noreply.github.com> Co-authored-by: Matthew Kevins <mmkevins@yahoo.com> Co-authored-by: Matthew Kevins <mkevins@users.noreply.github.com> Co-authored-by: Antonis Lilis <antonis.lilis@automattic.com> --- .../src/components/provider/index.native.js | 16 +++++++++++++--- .../mobile/WPAndroidGlue/GutenbergProps.kt | 6 ++++++ packages/react-native-bridge/ios/Gutenberg.swift | 1 + .../ios/GutenbergBridgeDataSource.swift | 1 + packages/react-native-editor/src/index.js | 2 ++ 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/components/provider/index.native.js b/packages/editor/src/components/provider/index.native.js index 8c9d7f8089bcfb..470a8fdf729ef7 100644 --- a/packages/editor/src/components/provider/index.native.js +++ b/packages/editor/src/components/provider/index.native.js @@ -89,10 +89,15 @@ class NativeEditorProvider extends Component { } componentDidMount() { - const { capabilities, updateSettings } = this.props; + const { + capabilities, + updateSettings, + galleryWithImageBlocks, + } = this.props; updateSettings( { ...capabilities, + ...{ __unstableGalleryWithImageBlocks: galleryWithImageBlocks }, ...this.getThemeColors( this.props ), } ); @@ -142,8 +147,13 @@ class NativeEditorProvider extends Component { this.subscriptionParentUpdateEditorSettings = subscribeUpdateEditorSettings( ( editorSettings ) => { - const themeColors = this.getThemeColors( editorSettings ); - updateSettings( themeColors ); + updateSettings( { + ...{ + __unstableGalleryWithImageBlocks: + editorSettings.galleryWithImageBlocks, + }, + ...this.getThemeColors( editorSettings ), + } ); } ); diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt index a08225d57d422a..5b35db5335f8df 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt @@ -41,6 +41,10 @@ data class GutenbergProps @JvmOverloads constructor( ?.let { putSerializable(PROP_STYLES, it) } theme.getSerializable(PROP_FEATURES) ?.let { putSerializable(PROP_FEATURES, it) } + theme.getSerializable(PROP_IS_FSE_THEME) + ?.let { putSerializable(PROP_IS_FSE_THEME, it) } + theme.getSerializable(PROP_GALLERY_WITH_IMAGE_BLOCKS) + ?.let { putSerializable(PROP_GALLERY_WITH_IMAGE_BLOCKS, it) } } } @@ -77,6 +81,8 @@ data class GutenbergProps @JvmOverloads constructor( private const val PROP_GRADIENTS = "gradients" private const val PROP_STYLES = "rawStyles" private const val PROP_FEATURES = "rawFeatures" + private const val PROP_IS_FSE_THEME = "isFSETheme" + private const val PROP_GALLERY_WITH_IMAGE_BLOCKS = "galleryWithImageBlocks" const val PROP_CAPABILITIES = "capabilities" const val PROP_CAPABILITIES_CONTACT_INFO_BLOCK = "contactInfoBlock" diff --git a/packages/react-native-bridge/ios/Gutenberg.swift b/packages/react-native-bridge/ios/Gutenberg.swift index 778967c66685b8..b926c36cb3bf30 100644 --- a/packages/react-native-bridge/ios/Gutenberg.swift +++ b/packages/react-native-bridge/ios/Gutenberg.swift @@ -199,6 +199,7 @@ public class Gutenberg: NSObject { private func properties(from editorSettings: GutenbergEditorSettings?) -> [String : Any] { var settingsUpdates = [String : Any]() settingsUpdates["isFSETheme"] = editorSettings?.isFSETheme ?? false + settingsUpdates["galleryWithImageBlocks"] = editorSettings?.galleryWithImageBlocks ?? false if let rawStyles = editorSettings?.rawStyles { settingsUpdates["rawStyles"] = rawStyles diff --git a/packages/react-native-bridge/ios/GutenbergBridgeDataSource.swift b/packages/react-native-bridge/ios/GutenbergBridgeDataSource.swift index 166b257fd33197..d2cb398a36bc94 100644 --- a/packages/react-native-bridge/ios/GutenbergBridgeDataSource.swift +++ b/packages/react-native-bridge/ios/GutenbergBridgeDataSource.swift @@ -77,6 +77,7 @@ public extension GutenbergBridgeDataSource { public protocol GutenbergEditorSettings { var isFSETheme: Bool { get } + var galleryWithImageBlocks: Bool { get } var rawStyles: String? { get } var rawFeatures: String? { get } var colors: [[String: String]]? { get } diff --git a/packages/react-native-editor/src/index.js b/packages/react-native-editor/src/index.js index c8b8fd8b152ab2..3fd18a16746487 100644 --- a/packages/react-native-editor/src/index.js +++ b/packages/react-native-editor/src/index.js @@ -83,6 +83,7 @@ const setupInitHooks = () => { gradients, rawStyles, rawFeatures, + galleryWithImageBlocks, } = props; if ( initialData === undefined && __DEV__ ) { @@ -110,6 +111,7 @@ const setupInitHooks = () => { gradients, rawStyles, rawFeatures, + galleryWithImageBlocks, }; } ); From 9c17b80d35e035438812d4bd8d7d8f3867887cb0 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Thu, 2 Sep 2021 11:48:53 +0200 Subject: [PATCH 115/214] Fix responsive menu height regression. (#34488) --- packages/block-library/src/navigation/style.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index 5d188709b39801..eef14229835402 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -510,6 +510,8 @@ position: relative; opacity: 1; visibility: visible; + height: auto; + width: auto; padding: 0 0 0 32px; border: none; From bf060dbdc6d4aa40736a6559455a373e4850e0d0 Mon Sep 17 00:00:00 2001 From: Ben Dwyer <ben@scruffian.com> Date: Thu, 2 Sep 2021 10:51:00 +0100 Subject: [PATCH 116/214] Consolidate the PATHS_WITH_MERGE constant to one instance (#34407) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Consolidate the PATHS_WITH_MERGE constant to one instance * Prefix name with EXPERIMENTAL_ * Update packages/blocks/src/api/constants.js Co-authored-by: André <583546+oandregal@users.noreply.github.com> --- packages/block-editor/src/components/use-setting/index.js | 8 +------- packages/blocks/src/api/constants.js | 7 +++++++ packages/blocks/src/api/index.js | 1 + packages/edit-site/src/components/editor/utils.js | 8 +------- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/use-setting/index.js b/packages/block-editor/src/components/use-setting/index.js index f82b7e38f0d2ec..74f03a3545cdd6 100644 --- a/packages/block-editor/src/components/use-setting/index.js +++ b/packages/block-editor/src/components/use-setting/index.js @@ -7,6 +7,7 @@ import { get } from 'lodash'; * WordPress dependencies */ import { useSelect } from '@wordpress/data'; +import { __EXPERIMENTAL_PATHS_WITH_MERGE as PATHS_WITH_MERGE } from '@wordpress/blocks'; /** * Internal dependencies @@ -49,13 +50,6 @@ const deprecatedFlags = { 'spacing.customPadding': ( settings ) => settings.enableCustomSpacing, }; -const PATHS_WITH_MERGE = { - 'color.gradients': true, - 'color.palette': true, - 'typography.fontFamilies': true, - 'typography.fontSizes': true, -}; - /** * Hook that retrieves the editor setting. * It works with nested objects using by finding the value at path. diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index 5564b339ec4a41..e60d868fc2227b 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -126,3 +126,10 @@ export const __EXPERIMENTAL_ELEMENTS = { h5: 'h5', h6: 'h6', }; + +export const __EXPERIMENTAL_PATHS_WITH_MERGE = { + 'color.gradients': true, + 'color.palette': true, + 'typography.fontFamilies': true, + 'typography.fontSizes': true, +}; diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index 394fddaa5de6e3..e567cde21e74bb 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -159,4 +159,5 @@ export { default as node } from './node'; export { __EXPERIMENTAL_STYLE_PROPERTY, __EXPERIMENTAL_ELEMENTS, + __EXPERIMENTAL_PATHS_WITH_MERGE, } from './constants'; diff --git a/packages/edit-site/src/components/editor/utils.js b/packages/edit-site/src/components/editor/utils.js index ba883f330ea264..81a809dbf4beee 100644 --- a/packages/edit-site/src/components/editor/utils.js +++ b/packages/edit-site/src/components/editor/utils.js @@ -6,6 +6,7 @@ import { get, find, forEach, camelCase, isString } from 'lodash'; * WordPress dependencies */ import { useSelect } from '@wordpress/data'; +import { __EXPERIMENTAL_PATHS_WITH_MERGE as PATHS_WITH_MERGE } from '@wordpress/blocks'; /** * Internal dependencies */ @@ -91,13 +92,6 @@ function getPresetMetadataFromStyleProperty( styleProperty ) { return getPresetMetadataFromStyleProperty.MAP[ styleProperty ]; } -const PATHS_WITH_MERGE = { - 'color.gradients': true, - 'color.palette': true, - 'typography.fontFamilies': true, - 'typography.fontSizes': true, -}; - export function useSetting( path, blockName = '' ) { const settings = useSelect( ( select ) => { return select( editSiteStore ).getSettings(); From 75932b1b977760b928181d52c774ef28c285c22f Mon Sep 17 00:00:00 2001 From: Ceyhun Ozugur <ceyhunozugur@gmail.com> Date: Thu, 2 Sep 2021 13:19:30 +0200 Subject: [PATCH 117/214] [RNMobile][Embed block] Fix bottom sheet link color in dark mode (#34424) --- .../footer-message-link.native.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/components/src/mobile/bottom-sheet/footer-message-link/footer-message-link.native.js b/packages/components/src/mobile/bottom-sheet/footer-message-link/footer-message-link.native.js index cdd478a958ef0e..87a9ae0813fd0d 100644 --- a/packages/components/src/mobile/bottom-sheet/footer-message-link/footer-message-link.native.js +++ b/packages/components/src/mobile/bottom-sheet/footer-message-link/footer-message-link.native.js @@ -5,21 +5,22 @@ import { Text, Linking } from 'react-native'; /** * WordPress dependencies */ -import { withPreferredColorScheme } from '@wordpress/compose'; +import { usePreferredColorSchemeStyle } from '@wordpress/compose'; /** * Internal dependencies */ import styles from './styles.scss'; function FooterMessageLink( { href, value } ) { + const textStyle = usePreferredColorSchemeStyle( + styles.footerMessageLink, + styles.footerMessageLinkDark + ); return ( - <Text - style={ styles.footerMessageLink } - onPress={ () => Linking.openURL( href ) } - > + <Text style={ textStyle } onPress={ () => Linking.openURL( href ) }> { value } </Text> ); } -export default withPreferredColorScheme( FooterMessageLink ); +export default FooterMessageLink; From f005524e8b6bc4d50fb710dc85be984c07fb0145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petter=20Walb=C3=B8=20Johnsg=C3=A5rd?= <petter@dekode.no> Date: Thu, 2 Sep 2021 13:51:24 +0200 Subject: [PATCH 118/214] Scripts: Only use svgr/webpack in js files (#34394) * Scripts: Only use svgr/webpack in js files * Add CHHANGELOG entry Co-authored-by: Grzegorz Ziolkowski <grzegorz@gziolo.pl> --- packages/scripts/CHANGELOG.md | 4 ++++ packages/scripts/config/webpack.config.js | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index adaf85dc90af5a..3ff2f06c87ae31 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fixes + +- Bring back support for SVG files in CSS ([#34394](https://github.com/WordPress/gutenberg/pull/34394)). It wasn't correctly migrated when integrating webpack v5. + ## 18.0.0 (2021-08-23) ### Breaking Changes diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 86ed99807d8815..5bbc0a43e18b90 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -184,9 +184,15 @@ const config = { }, { test: /\.svg$/, + issuer: /\.jsx?$/, use: [ '@svgr/webpack', 'url-loader' ], type: 'javascript/auto', }, + { + test: /\.svg$/, + issuer: /\.(sc|sa|c)ss$/, + type: 'asset/inline', + }, { test: /\.(bmp|png|jpe?g|gif)$/i, type: 'asset/resource', From e2e3f6b30c34cc0f70bc59f0ebc283dc57fbe149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <4710635+ellatrix@users.noreply.github.com> Date: Thu, 2 Sep 2021 15:42:48 +0300 Subject: [PATCH 119/214] Rich text: replace global event handlers with local ones (#34492) --- .../src/components/rich-text/index.js | 40 +++++++++++++----- .../src/components/rich-text/input-event.js | 41 ++++++++++--------- .../src/components/rich-text/shortcut.js | 33 +++++++++++---- .../components/rich-text/use-input-events.js | 19 +++++++++ .../src/components/rich-text/use-shortcuts.js | 19 +++++++++ 5 files changed, 112 insertions(+), 40 deletions(-) create mode 100644 packages/block-editor/src/components/rich-text/use-input-events.js create mode 100644 packages/block-editor/src/components/rich-text/use-shortcuts.js diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 807a9014850155..fe4f20cf3d41d1 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -7,7 +7,13 @@ import { omit } from 'lodash'; /** * WordPress dependencies */ -import { RawHTML, useRef, useCallback, forwardRef } from '@wordpress/element'; +import { + RawHTML, + useRef, + useCallback, + forwardRef, + createContext, +} from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { children as childrenSource } from '@wordpress/blocks'; import { useInstanceId, useMergeRefs } from '@wordpress/compose'; @@ -36,9 +42,14 @@ import { useInputRules } from './use-input-rules'; import { useEnter } from './use-enter'; import { useFormatTypes } from './use-format-types'; import { useRemoveBrowserShortcuts } from './use-remove-browser-shortcuts'; +import { useShortcuts } from './use-shortcuts'; +import { useInputEvents } from './use-input-events'; import FormatEdit from './format-edit'; import { getMultilineTag, getAllowedFormats } from './utils'; +export const keyboardShortcutContext = createContext(); +export const inputEventContext = createContext(); + /** * Removes props used for the native version of RichText so that they are not * passed to the DOM element and log warnings. @@ -244,6 +255,9 @@ function RichTextWrapper( useCaretInFormat( { value } ); useMarkPersistent( { html: adjustedValue, value } ); + const keyboardShortcuts = useRef( new Set() ); + const inputEvents = useRef( new Set() ); + function onKeyDown( event ) { const { keyCode } = event; @@ -286,17 +300,19 @@ function RichTextWrapper( const TagName = tagName; const content = ( <> - { isSelected && - children && - children( { value, onChange, onFocus } ) } { isSelected && ( - <FormatEdit - value={ value } - onChange={ onChange } - onFocus={ onFocus } - formatTypes={ formatTypes } - forwardedRef={ anchorRef } - /> + <keyboardShortcutContext.Provider value={ keyboardShortcuts }> + <inputEventContext.Provider value={ inputEvents }> + { children && children( { value, onChange, onFocus } ) } + <FormatEdit + value={ value } + onChange={ onChange } + onFocus={ onFocus } + formatTypes={ formatTypes } + forwardedRef={ anchorRef } + /> + </inputEventContext.Provider> + </keyboardShortcutContext.Provider> ) } { isSelected && hasFormats && ( <FormatToolbarContainer @@ -323,6 +339,8 @@ function RichTextWrapper( onReplace, } ), useRemoveBrowserShortcuts(), + useShortcuts( keyboardShortcuts ), + useInputEvents( inputEvents ), useUndoAutomaticChange(), usePasteHandler( { isSelected, diff --git a/packages/block-editor/src/components/rich-text/input-event.js b/packages/block-editor/src/components/rich-text/input-event.js index 016e3700956811..ab79a9a51dd98b 100644 --- a/packages/block-editor/src/components/rich-text/input-event.js +++ b/packages/block-editor/src/components/rich-text/input-event.js @@ -1,30 +1,31 @@ /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { useEffect, useContext, useRef } from '@wordpress/element'; -export class __unstableRichTextInputEvent extends Component { - constructor() { - super( ...arguments ); +/** + * Internal dependencies + */ +import { inputEventContext } from './'; - this.onInput = this.onInput.bind( this ); - } +export function __unstableRichTextInputEvent( { inputType, onInput } ) { + const callbacks = useContext( inputEventContext ); + const onInputRef = useRef(); + onInputRef.current = onInput; - onInput( event ) { - if ( event.inputType === this.props.inputType ) { - this.props.onInput(); + useEffect( () => { + function callback( event ) { + if ( event.inputType === inputType ) { + onInputRef.current(); + event.preventDefault(); + } } - } - - componentDidMount() { - document.addEventListener( 'input', this.onInput, true ); - } - componentWillUnmount() { - document.removeEventListener( 'input', this.onInput, true ); - } + callbacks.current.add( callback ); + return () => { + callbacks.current.delete( callback ); + }; + }, [ inputType ] ); - render() { - return null; - } + return null; } diff --git a/packages/block-editor/src/components/rich-text/shortcut.js b/packages/block-editor/src/components/rich-text/shortcut.js index 4f93c6c99fad14..05aa0bcb61ddca 100644 --- a/packages/block-editor/src/components/rich-text/shortcut.js +++ b/packages/block-editor/src/components/rich-text/shortcut.js @@ -1,17 +1,32 @@ /** * WordPress dependencies */ -import { useKeyboardShortcut } from '@wordpress/compose'; -import { rawShortcut } from '@wordpress/keycodes'; +import { isKeyboardEvent } from '@wordpress/keycodes'; +import { useEffect, useContext, useRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { keyboardShortcutContext } from './'; export function RichTextShortcut( { character, type, onUse } ) { - const callback = () => { - onUse(); - return false; - }; - useKeyboardShortcut( rawShortcut[ type ]( character ), callback, { - bindGlobal: true, - } ); + const keyboardShortcuts = useContext( keyboardShortcutContext ); + const onUseRef = useRef(); + onUseRef.current = onUse; + + useEffect( () => { + function callback( event ) { + if ( isKeyboardEvent[ type ]( event, character ) ) { + onUseRef.current(); + event.preventDefault(); + } + } + + keyboardShortcuts.current.add( callback ); + return () => { + keyboardShortcuts.current.delete( callback ); + }; + }, [ character, type ] ); return null; } diff --git a/packages/block-editor/src/components/rich-text/use-input-events.js b/packages/block-editor/src/components/rich-text/use-input-events.js new file mode 100644 index 00000000000000..4305e126fda7e7 --- /dev/null +++ b/packages/block-editor/src/components/rich-text/use-input-events.js @@ -0,0 +1,19 @@ +/** + * WordPress dependencies + */ +import { useRefEffect } from '@wordpress/compose'; + +export function useInputEvents( inputEvents ) { + return useRefEffect( ( element ) => { + function onInput( event ) { + for ( const keyboardShortcut of inputEvents.current ) { + keyboardShortcut( event ); + } + } + + element.addEventListener( 'input', onInput ); + return () => { + element.removeEventListener( 'input', onInput ); + }; + }, [] ); +} diff --git a/packages/block-editor/src/components/rich-text/use-shortcuts.js b/packages/block-editor/src/components/rich-text/use-shortcuts.js new file mode 100644 index 00000000000000..61b2e3f6ce31bc --- /dev/null +++ b/packages/block-editor/src/components/rich-text/use-shortcuts.js @@ -0,0 +1,19 @@ +/** + * WordPress dependencies + */ +import { useRefEffect } from '@wordpress/compose'; + +export function useShortcuts( keyboardShortcuts ) { + return useRefEffect( ( element ) => { + function onKeyDown( event ) { + for ( const keyboardShortcut of keyboardShortcuts.current ) { + keyboardShortcut( event ); + } + } + + element.addEventListener( 'keydown', onKeyDown ); + return () => { + element.removeEventListener( 'keydown', onKeyDown ); + }; + }, [] ); +} From 4eb90e8068ee8f2c80b80db18d77472ca6dd0bfb Mon Sep 17 00:00:00 2001 From: annezazu <annezazu@gmail.com> Date: Thu, 2 Sep 2021 07:26:40 -0600 Subject: [PATCH 120/214] Update the Table block description (#34475) This minor update expands the language used to make it more descriptive. --- packages/block-library/src/table/block.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/table/block.json b/packages/block-library/src/table/block.json index 01e5c58520d554..f4723331098bc9 100644 --- a/packages/block-library/src/table/block.json +++ b/packages/block-library/src/table/block.json @@ -3,7 +3,7 @@ "name": "core/table", "title": "Table", "category": "text", - "description": "Insert a table — perfect for sharing charts and data.", + "description": "Create structured content in rows and columns to display information.", "textdomain": "default", "attributes": { "hasFixedLayout": { From 8f4726f339332f4008e32e9906c93ee72a1f791b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <4710635+ellatrix@users.noreply.github.com> Date: Thu, 2 Sep 2021 16:27:47 +0300 Subject: [PATCH 121/214] Button block: replace global shortcut event handlers with local ones (#34498) --- packages/block-library/src/button/edit.js | 238 ++++++++++------------ 1 file changed, 102 insertions(+), 136 deletions(-) diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 0520144fb3b9ca..aa79ee58f67d60 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -11,7 +11,6 @@ import { useCallback, useEffect, useState, useRef } from '@wordpress/element'; import { Button, ButtonGroup, - KeyboardShortcuts, PanelBody, TextControl, ToolbarButton, @@ -27,7 +26,7 @@ import { __experimentalGetSpacingClassesAndStyles as useSpacingProps, __experimentalLinkControl as LinkControl, } from '@wordpress/block-editor'; -import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; +import { displayShortcut, isKeyboardEvent } from '@wordpress/keycodes'; import { link, linkOff } from '@wordpress/icons'; import { createBlock } from '@wordpress/blocks'; @@ -66,112 +65,6 @@ function WidthPanel( { selectedWidth, setAttributes } ) { ); } -function URLPicker( { - isSelected, - url, - setAttributes, - opensInNewTab, - onToggleOpenInNewTab, - anchorRef, - richTextRef, -} ) { - const [ isEditingURL, setIsEditingURL ] = useState( false ); - const isURLSet = !! url; - - const startEditing = ( event ) => { - event.preventDefault(); - setIsEditingURL( true ); - }; - - const unlink = () => { - setAttributes( { - url: undefined, - linkTarget: undefined, - rel: undefined, - } ); - setIsEditingURL( false ); - }; - - useEffect( () => { - if ( ! isSelected ) { - setIsEditingURL( false ); - } - }, [ isSelected ] ); - - const isLinkControlVisible = isSelected && ( isEditingURL || isURLSet ); - - const linkControl = isLinkControlVisible && ( - <Popover - position="bottom center" - onClose={ () => { - setIsEditingURL( false ); - richTextRef.current?.focus(); - } } - anchorRef={ anchorRef?.current } - focusOnMount={ isEditingURL ? 'firstElement' : false } - > - <LinkControl - className="wp-block-navigation-link__inline-link-input" - value={ { url, opensInNewTab } } - onChange={ ( { - url: newURL = '', - opensInNewTab: newOpensInNewTab, - } ) => { - setAttributes( { url: newURL } ); - - if ( opensInNewTab !== newOpensInNewTab ) { - onToggleOpenInNewTab( newOpensInNewTab ); - } - } } - onRemove={ () => { - unlink(); - richTextRef.current?.focus(); - } } - forceIsEditingLink={ isEditingURL } - /> - </Popover> - ); - - return ( - <> - <BlockControls group="block"> - { ! isURLSet && ( - <ToolbarButton - name="link" - icon={ link } - title={ __( 'Link' ) } - shortcut={ displayShortcut.primary( 'k' ) } - onClick={ startEditing } - /> - ) } - { isURLSet && ( - <ToolbarButton - name="link" - icon={ linkOff } - title={ __( 'Unlink' ) } - shortcut={ displayShortcut.primaryShift( 'k' ) } - onClick={ unlink } - isActive={ true } - /> - ) } - </BlockControls> - { isSelected && ( - <KeyboardShortcuts - bindGlobal - shortcuts={ { - [ rawShortcut.primary( 'k' ) ]: startEditing, - [ rawShortcut.primaryShift( 'k' ) ]: () => { - unlink(); - richTextRef.current?.focus(); - }, - } } - /> - ) } - { linkControl } - </> - ); -} - function ButtonEdit( props ) { const { attributes, @@ -197,36 +90,66 @@ function ButtonEdit( props ) { [ setAttributes ] ); - const onToggleOpenInNewTab = useCallback( - ( value ) => { - const newLinkTarget = value ? '_blank' : undefined; + function onToggleOpenInNewTab( value ) { + const newLinkTarget = value ? '_blank' : undefined; - let updatedRel = rel; - if ( newLinkTarget && ! rel ) { - updatedRel = NEW_TAB_REL; - } else if ( ! newLinkTarget && rel === NEW_TAB_REL ) { - updatedRel = undefined; - } + let updatedRel = rel; + if ( newLinkTarget && ! rel ) { + updatedRel = NEW_TAB_REL; + } else if ( ! newLinkTarget && rel === NEW_TAB_REL ) { + updatedRel = undefined; + } - setAttributes( { - linkTarget: newLinkTarget, - rel: updatedRel, - } ); - }, - [ rel, setAttributes ] - ); + setAttributes( { + linkTarget: newLinkTarget, + rel: updatedRel, + } ); + } - const setButtonText = ( newText ) => { + function setButtonText( newText ) { // Remove anchor tags from button text content. setAttributes( { text: newText.replace( /<\/?a[^>]*>/g, '' ) } ); - }; + } + + function onKeyDown( event ) { + if ( isKeyboardEvent.primary( event, 'k' ) ) { + startEditing( event ); + } else if ( isKeyboardEvent.primaryShift( event, 'k' ) ) { + unlink(); + richTextRef.current?.focus(); + } + } const borderProps = useBorderProps( attributes ); const colorProps = useColorProps( attributes ); const spacingProps = useSpacingProps( attributes ); const ref = useRef(); const richTextRef = useRef(); - const blockProps = useBlockProps( { ref } ); + const blockProps = useBlockProps( { ref, onKeyDown } ); + + const [ isEditingURL, setIsEditingURL ] = useState( false ); + const isURLSet = !! url; + const opensInNewTab = linkTarget === '_blank'; + + function startEditing( event ) { + event.preventDefault(); + setIsEditingURL( true ); + } + + function unlink() { + setAttributes( { + url: undefined, + linkTarget: undefined, + rel: undefined, + } ); + setIsEditingURL( false ); + } + + useEffect( () => { + if ( ! isSelected ) { + setIsEditingURL( false ); + } + }, [ isSelected ] ); return ( <> @@ -271,15 +194,58 @@ function ButtonEdit( props ) { identifier="text" /> </div> - <URLPicker - url={ url } - setAttributes={ setAttributes } - isSelected={ isSelected } - opensInNewTab={ linkTarget === '_blank' } - onToggleOpenInNewTab={ onToggleOpenInNewTab } - anchorRef={ ref } - richTextRef={ richTextRef } - /> + <BlockControls group="block"> + { ! isURLSet && ( + <ToolbarButton + name="link" + icon={ link } + title={ __( 'Link' ) } + shortcut={ displayShortcut.primary( 'k' ) } + onClick={ startEditing } + /> + ) } + { isURLSet && ( + <ToolbarButton + name="link" + icon={ linkOff } + title={ __( 'Unlink' ) } + shortcut={ displayShortcut.primaryShift( 'k' ) } + onClick={ unlink } + isActive={ true } + /> + ) } + </BlockControls> + { isSelected && ( isEditingURL || isURLSet ) && ( + <Popover + position="bottom center" + onClose={ () => { + setIsEditingURL( false ); + richTextRef.current?.focus(); + } } + anchorRef={ ref?.current } + focusOnMount={ isEditingURL ? 'firstElement' : false } + > + <LinkControl + className="wp-block-navigation-link__inline-link-input" + value={ { url, opensInNewTab } } + onChange={ ( { + url: newURL = '', + opensInNewTab: newOpensInNewTab, + } ) => { + setAttributes( { url: newURL } ); + + if ( opensInNewTab !== newOpensInNewTab ) { + onToggleOpenInNewTab( newOpensInNewTab ); + } + } } + onRemove={ () => { + unlink(); + richTextRef.current?.focus(); + } } + forceIsEditingLink={ isEditingURL } + /> + </Popover> + ) } <InspectorControls> <WidthPanel selectedWidth={ width } From 866af4fa487d039965e141b012341cc9fcad30d3 Mon Sep 17 00:00:00 2001 From: Maggie <maggie.cabrera@automattic.com> Date: Thu, 2 Sep 2021 15:44:41 +0200 Subject: [PATCH 122/214] add duotone suport to the post author block (#34408) --- packages/block-library/src/post-author/block.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/post-author/block.json b/packages/block-library/src/post-author/block.json index f82c5662abeae9..4839069ed4a1bc 100644 --- a/packages/block-library/src/post-author/block.json +++ b/packages/block-library/src/post-author/block.json @@ -33,7 +33,8 @@ }, "color": { "gradients": true, - "link": true + "link": true, + "__experimentalDuotone": ".wp-block-post-author__avatar img" } }, "editorStyle": "wp-block-post-author-editor", From 594cc7d3e552d9ffbfdc9210c707e2ee6413bd99 Mon Sep 17 00:00:00 2001 From: Stefanos Togoulidis <stefanostogoulidis@gmail.com> Date: Thu, 2 Sep 2021 17:07:42 +0300 Subject: [PATCH 123/214] [RNMobile] Mention the embed variant the preview of which is WIP (#34495) * Mention the embed variant the preview of which is WIP * Import sprintf * Add info for translators --- .../src/embed/embed-no-preview.native.js | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/block-library/src/embed/embed-no-preview.native.js b/packages/block-library/src/embed/embed-no-preview.native.js index cd0af7b7308514..c76ea855eff5f8 100644 --- a/packages/block-library/src/embed/embed-no-preview.native.js +++ b/packages/block-library/src/embed/embed-no-preview.native.js @@ -8,7 +8,7 @@ import { TouchableOpacity, TouchableWithoutFeedback, Text } from 'react-native'; */ import { View } from '@wordpress/primitives'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useRef, useState } from '@wordpress/element'; import { usePreferredColorSchemeStyle } from '@wordpress/compose'; import { requestPreview } from '@wordpress/react-native-bridge'; @@ -73,11 +73,19 @@ const EmbedNoPreview = ( { label, icon, isSelected, onPress } ) => { postType === 'page' ? __( 'Preview page' ) : __( 'Preview post' ); const comingSoonDescription = postType === 'page' - ? __( - 'We’re working hard on adding support for embed previews. In the meantime, you can preview the embedded content on the page.' + ? sprintf( + // translators: %s: embed block variant's label e.g: "Twitter". + __( + 'We’re working hard on adding support for %s previews. In the meantime, you can preview the embedded content on the page.' + ), + label ) - : __( - 'We’re working hard on adding support for embed previews. In the meantime, you can preview the embedded content on the post.' + : sprintf( + // translators: %s: embed block variant's label e.g: "Twitter". + __( + 'We’re working hard on adding support for %s previews. In the meantime, you can preview the embedded content on the post.' + ), + label ); function onOpenSheet() { @@ -118,7 +126,11 @@ const EmbedNoPreview = ( { label, icon, isSelected, onPress } ) => { <BlockIcon icon={ icon } /> <Text style={ labelStyle }>{ label }</Text> <Text style={ descriptionStyle }> - { __( 'Embed previews not yet available' ) } + { sprintf( + // translators: %s: embed block variant's label e.g: "Twitter". + __( '%s previews not yet available' ), + label + ) } </Text> <Text style={ styles.embed__action }> { previewButtonText.toUpperCase() } @@ -154,7 +166,11 @@ const EmbedNoPreview = ( { label, icon, isSelected, onPress } ) => { /> </View> <Text style={ sheetTitleStyle }> - { __( 'Embed block previews are coming soon' ) } + { sprintf( + // translators: %s: embed block variant's label e.g: "Twitter". + __( '%s block previews are coming soon' ), + label + ) } </Text> <Text style={ sheetDescriptionStyle }> { comingSoonDescription } From ec8632ec74d39d035af318a3c31532a2b6efc609 Mon Sep 17 00:00:00 2001 From: Antonis Lilis <antonis.lilis@automattic.com> Date: Thu, 2 Sep 2021 17:15:24 +0300 Subject: [PATCH 124/214] Mobile - Block Selection Button - Use BlockIcon wrapper (#34499) Co-authored-by: Gerardo Pacheco <gerardo.pacheco@automattic.com> --- .../src/components/block-icon/index.native.js | 13 +++++++++---- .../block-list/block-selection-button.native.js | 13 ++++++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/block-icon/index.native.js b/packages/block-editor/src/components/block-icon/index.native.js index 3e1bca650976a2..4926949f88d563 100644 --- a/packages/block-editor/src/components/block-icon/index.native.js +++ b/packages/block-editor/src/components/block-icon/index.native.js @@ -17,6 +17,8 @@ import styles from './style.scss'; export function BlockIcon( { icon, + fill, + size, showColors = false, getStylesFromColorScheme, } ) { @@ -29,10 +31,13 @@ export function BlockIcon( { const renderedIcon = ( <Icon icon={ icon && icon.src ? icon.src : icon } - { ...getStylesFromColorScheme( - styles.iconPlaceholder, - styles.iconPlaceholderDark - ) } + { ...( fill && { fill } ) } + { ...( size && { size } ) } + { ...( ! fill && + getStylesFromColorScheme( + styles.iconPlaceholder, + styles.iconPlaceholderDark + ) ) } /> ); const style = showColors diff --git a/packages/block-editor/src/components/block-list/block-selection-button.native.js b/packages/block-editor/src/components/block-list/block-selection-button.native.js index 97a790482c7654..ef066caf3d3f37 100644 --- a/packages/block-editor/src/components/block-list/block-selection-button.native.js +++ b/packages/block-editor/src/components/block-list/block-selection-button.native.js @@ -5,6 +5,7 @@ import { Icon } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { getBlockType } from '@wordpress/blocks'; +import { BlockIcon } from '@wordpress/block-editor'; /** * External dependencies @@ -58,11 +59,13 @@ const BlockSelectionButton = ( { /> </View>, ] } - <Icon - size={ 24 } - icon={ blockInformation?.icon?.src } - fill={ styles.icon.color } - /> + { blockInformation?.icon && ( + <BlockIcon + size={ 24 } + icon={ blockInformation.icon } + fill={ styles.icon.color } + /> + ) } <Text maxFontSizeMultiplier={ 1.25 } ellipsizeMode="tail" From 0e5d2a1694f38cd10eae05359c674bdd19a5c2eb Mon Sep 17 00:00:00 2001 From: annezazu <annezazu@gmail.com> Date: Thu, 2 Sep 2021 08:42:49 -0600 Subject: [PATCH 125/214] Fix title missing in bug report form (#34504) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix title missing in bug report form Looks like in https://github.com/WordPress/gutenberg/pull/34458/commits/33ef9edf7563a21435218a628c6947d722f8f021 the title was accidentally removed! I managed to miss this. This is a quick PR to resolve since the form isn't showing currently. * Remove title property entirely Co-authored-by: Héctor <27339341+priethor@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/Bug_report.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.yml b/.github/ISSUE_TEMPLATE/Bug_report.yml index df4e13eb58d91e..50944218a52b86 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.yml +++ b/.github/ISSUE_TEMPLATE/Bug_report.yml @@ -1,6 +1,5 @@ name: Bug report description: Report a bug with the WordPress block editor or Gutenberg plugin -title: '' body: - type: markdown attributes: From 482650ac91cc7b6ad5b595d563b081c389124759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Thu, 2 Sep 2021 16:51:31 +0200 Subject: [PATCH 126/214] Make global styles available to all themes (#34334) They just differ on what data they have: 1. Without theme.json and no experimental-link-color support either: the stylesheet contains the core presets & styles. 2. Without theme.json but experimental-link-color support: the stylesheet contains the core and theme presets, plus the core styles if any. 3. With theme.json: the stylesheet contains the core and theme presets, plus the result of merging core & theme styles. --- lib/class-wp-theme-json-gutenberg.php | 65 ++++++++++++------- ...class-wp-theme-json-resolver-gutenberg.php | 11 +++- lib/global-styles.php | 14 ++-- 3 files changed, 55 insertions(+), 35 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index cf713529b4f70d..ee12dad41fa089 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -799,8 +799,33 @@ private function get_css_variables( $nodes ) { * style-property-one: value; * } * - * Additionally, it'll also create new rulesets - * as classes for each preset value such as: + * @param array $style_nodes Nodes with styles. + * + * @return string The new stylesheet. + */ + private function get_block_classes( $style_nodes ) { + $block_rules = ''; + + foreach ( $style_nodes as $metadata ) { + if ( null === $metadata['selector'] ) { + continue; + } + + $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); + $selector = $metadata['selector']; + $declarations = self::compute_style_properties( $node ); + $block_rules .= self::to_ruleset( $selector, $declarations ); + + if ( self::ROOT_BLOCK_SELECTOR === $selector ) { + $block_rules .= '.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }'; + } + } + + return $block_rules; + } + + /** + * Creates new rulesets as classes for each preset value such as: * * .has-value-color { * color: value; @@ -821,30 +846,14 @@ private function get_css_variables( $nodes ) { * p.has-value-gradient-background { * background: value; * } - * - * @param array $style_nodes Nodes with styles. + * @param array $setting_nodes Nodes with settings. * * @return string The new stylesheet. */ - private function get_block_styles( $style_nodes, $setting_nodes ) { - $block_rules = ''; - foreach ( $style_nodes as $metadata ) { - if ( null === $metadata['selector'] ) { - continue; - } - - $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); - $selector = $metadata['selector']; - $declarations = self::compute_style_properties( $node ); - $block_rules .= self::to_ruleset( $selector, $declarations ); - - if ( self::ROOT_BLOCK_SELECTOR === $selector ) { - $block_rules .= '.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }'; - } - } - + private function get_preset_classes( $setting_nodes ) { $preset_rules = ''; + foreach ( $setting_nodes as $metadata ) { if ( null === $metadata['selector'] ) { continue; @@ -855,7 +864,7 @@ private function get_block_styles( $style_nodes, $setting_nodes ) { $preset_rules .= self::compute_preset_classes( $node, $selector ); } - return $block_rules . $preset_rules; + return $preset_rules; } /** @@ -1053,7 +1062,11 @@ private static function get_setting_nodes( $theme_json, $selectors = array() ) { * Returns the stylesheet that results of processing * the theme.json structure this object represents. * - * @param string $type Type of stylesheet we want accepts 'all', 'block_styles', and 'css_variables'. + * @param string $type Type of stylesheet. It accepts: + * 'all': css variables, block classes, preset classes. The default. + * 'block_styles': only block & preset classes. + * 'css_variables': only css variables. + * 'presets': only css variables and preset classes. * @return string Stylesheet. */ public function get_stylesheet( $type = 'all' ) { @@ -1063,11 +1076,13 @@ public function get_stylesheet( $type = 'all' ) { switch ( $type ) { case 'block_styles': - return $this->get_block_styles( $style_nodes, $setting_nodes ); + return $this->get_block_classes( $style_nodes ) . $this->get_preset_classes( $setting_nodes ); case 'css_variables': return $this->get_css_variables( $setting_nodes ); + case 'presets': + return $this->get_css_variables( $setting_nodes ) . $this->get_preset_classes( $setting_nodes ); default: - return $this->get_css_variables( $setting_nodes ) . $this->get_block_styles( $style_nodes, $setting_nodes ); + return $this->get_css_variables( $setting_nodes ) . $this->get_block_classes( $style_nodes ) . $this->get_preset_classes( $setting_nodes ); } } diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index b96c43f8ddf4fe..a208cbd726d88e 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -407,10 +407,17 @@ public static function get_user_data() { * @return WP_Theme_JSON_Gutenberg */ public static function get_merged_data( $settings = array(), $origin = 'user' ) { - $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( $settings ); - $result = new WP_Theme_JSON_Gutenberg(); $result->merge( self::get_core_data() ); + + if ( + ! get_theme_support( 'experimental-link-color' ) && // link color support needs the presets CSS variables regardless of the presence of theme.json file. + ! WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() + ) { + return $result; + } + + $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( $settings ); $result->merge( self::get_theme_data( $theme_support_data ) ); if ( 'user' === $origin ) { diff --git a/lib/global-styles.php b/lib/global-styles.php index a749a66c40cf0d..9469b79d06448d 100644 --- a/lib/global-styles.php +++ b/lib/global-styles.php @@ -10,7 +10,7 @@ * the corresponding stylesheet. * * @param WP_Theme_JSON_Gutenberg $tree Input tree. - * @param string $type Type of stylesheet we want accepts 'all', 'block_styles', and 'css_variables'. + * @param string $type Type of stylesheet we want accepts 'all', 'block_styles', 'css_variables', and 'presets'. * * @return string Stylesheet. */ @@ -48,16 +48,14 @@ function gutenberg_experimental_global_styles_get_stylesheet( $tree, $type = 'al * and enqueues the resulting stylesheet. */ function gutenberg_experimental_global_styles_enqueue_assets() { - if ( - ! get_theme_support( 'experimental-link-color' ) && // link color support needs the presets CSS variables regardless of the presence of theme.json file. - ! WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) { - return; - } - $settings = gutenberg_get_default_block_editor_settings(); $all = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $settings ); - $stylesheet = gutenberg_experimental_global_styles_get_stylesheet( $all ); + $type = 'all'; + if ( ! WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) { + $type = 'presets'; + } + $stylesheet = gutenberg_experimental_global_styles_get_stylesheet( $all, $type ); if ( empty( $stylesheet ) ) { return; } From b082f245bec7edaddb7c908478ce910189199324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <4710635+ellatrix@users.noreply.github.com> Date: Thu, 2 Sep 2021 18:14:41 +0300 Subject: [PATCH 127/214] Components: Guide: replace global shortcut event handlers with local ones (#34503) --- packages/components/src/guide/index.js | 17 ++++++++--------- packages/components/src/modal/index.js | 2 ++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/components/src/guide/index.js b/packages/components/src/guide/index.js index dc0a4f22613e51..bc29f5ad274f4e 100644 --- a/packages/components/src/guide/index.js +++ b/packages/components/src/guide/index.js @@ -9,12 +9,12 @@ import classnames from 'classnames'; import { useState, useEffect, Children } from '@wordpress/element'; import deprecated from '@wordpress/deprecated'; import { __ } from '@wordpress/i18n'; +import { LEFT, RIGHT } from '@wordpress/keycodes'; /** * Internal dependencies */ import Modal from '../modal'; -import KeyboardShortcuts from '../keyboard-shortcuts'; import Button from '../button'; import PageControl from './page-control'; import FinishButton from './finish-button'; @@ -66,15 +66,14 @@ export default function Guide( { className={ classnames( 'components-guide', className ) } contentLabel={ contentLabel } onRequestClose={ onFinish } + onKeyDown={ ( event ) => { + if ( event.keyCode === LEFT ) { + goBack(); + } else if ( event.keyCode === RIGHT ) { + goForward(); + } + } } > - <KeyboardShortcuts - key={ currentPage } - shortcuts={ { - left: goBack, - right: goForward, - } } - /> - <div className="components-guide__container"> <div className="components-guide__page"> { pages[ currentPage ].image } diff --git a/packages/components/src/modal/index.js b/packages/components/src/modal/index.js index 0d4ec5de58f048..fe57b88409c94b 100644 --- a/packages/components/src/modal/index.js +++ b/packages/components/src/modal/index.js @@ -51,6 +51,7 @@ export default function Modal( { overlayClassName, className, contentLabel, + onKeyDown, } ) { const ref = useRef(); const instanceId = useInstanceId( Modal ); @@ -124,6 +125,7 @@ export default function Modal( { aria-describedby={ aria.describedby } tabIndex="-1" { ...( shouldCloseOnClickOutside ? focusOutsideProps : {} ) } + onKeyDown={ onKeyDown } > <div className={ 'components-modal__content' } role="document"> <div className="components-modal__header"> From 6ef8d95986879cf976853e1761409db43cbb9c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <4710635+ellatrix@users.noreply.github.com> Date: Thu, 2 Sep 2021 18:15:21 +0300 Subject: [PATCH 128/214] Navigation link block: replace global shortcut event handlers with local ones (#34500) --- .../block-library/src/navigation-link/edit.js | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 9df12f2681bc53..fa93ace9b5de53 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -10,7 +10,6 @@ import { escape } from 'lodash'; import { createBlock } from '@wordpress/blocks'; import { useSelect, useDispatch } from '@wordpress/data'; import { - KeyboardShortcuts, PanelBody, Popover, TextControl, @@ -18,7 +17,7 @@ import { ToolbarButton, ToolbarGroup, } from '@wordpress/components'; -import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; +import { displayShortcut, isKeyboardEvent, ENTER } from '@wordpress/keycodes'; import { __, sprintf } from '@wordpress/i18n'; import { BlockControls, @@ -467,6 +466,15 @@ export default function NavigationLinkEdit( { customBackgroundColor, } = getColors( context, ! isTopLevelLink ); + function onKeyDown( event ) { + if ( + isKeyboardEvent.primary( event, 'k' ) || + ( ! url && event.keyCode === ENTER ) + ) { + setIsLinkOpen( true ); + } + } + const blockProps = useBlockProps( { ref: listItemRef, className: classnames( 'wp-block-navigation-item', { @@ -486,6 +494,7 @@ export default function NavigationLinkEdit( { color: ! textColor && customTextColor, backgroundColor: ! backgroundColor && customBackgroundColor, }, + onKeyDown, } ); if ( ! url ) { @@ -566,13 +575,6 @@ export default function NavigationLinkEdit( { <Fragment> <BlockControls> <ToolbarGroup> - <KeyboardShortcuts - bindGlobal - shortcuts={ { - [ rawShortcut.primary( 'k' ) ]: () => - setIsLinkOpen( true ), - } } - /> <ToolbarButton name="link" icon={ linkIcon } @@ -626,12 +628,6 @@ export default function NavigationLinkEdit( { { /* eslint-enable */ } { ! url ? ( <div className="wp-block-navigation-link__placeholder-text"> - <KeyboardShortcuts - shortcuts={ { - enter: () => - isSelected && setIsLinkOpen( true ), - } } - /> { missingText } </div> ) : ( @@ -672,12 +668,6 @@ export default function NavigationLinkEdit( { onClose={ () => setIsLinkOpen( false ) } anchorRef={ listItemRef.current } > - <KeyboardShortcuts - bindGlobal - shortcuts={ { - escape: () => setIsLinkOpen( false ), - } } - /> <LinkControl className="wp-block-navigation-link__inline-link-input" value={ link } From e88e78b2761af80e9b579a308c510a01afa7c2f4 Mon Sep 17 00:00:00 2001 From: Marco Ciampini <marco.ciampo@gmail.com> Date: Thu, 2 Sep 2021 18:30:57 +0200 Subject: [PATCH 129/214] Components: fix selected value computation in `CustomSelectControl` when no initial `value` is set (#34490) --- packages/components/src/custom-select-control/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/components/src/custom-select-control/index.js b/packages/components/src/custom-select-control/index.js index c78c7f715e8689..b1498938ab8bf4 100644 --- a/packages/components/src/custom-select-control/index.js +++ b/packages/components/src/custom-select-control/index.js @@ -75,7 +75,9 @@ export default function CustomSelectControl( { items, itemToString, onSelectedItemChange, - selectedItem: _selectedItem, + ...( typeof _selectedItem !== 'undefined' && _selectedItem !== null + ? { selectedItem: _selectedItem } + : undefined ), stateReducer, } ); From 288ab5d44e1e48f3ca24574eb791dcef5431b7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <4710635+ellatrix@users.noreply.github.com> Date: Thu, 2 Sep 2021 19:51:28 +0300 Subject: [PATCH 130/214] Components: custom gradient bar: replace global shortcut event handlers with local ones (#34505) --- .../src/custom-gradient-bar/control-points.js | 83 ++++++++----------- 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/packages/components/src/custom-gradient-bar/control-points.js b/packages/components/src/custom-gradient-bar/control-points.js index e5edfc88820144..fc63c5896a5873 100644 --- a/packages/components/src/custom-gradient-bar/control-points.js +++ b/packages/components/src/custom-gradient-bar/control-points.js @@ -10,6 +10,7 @@ import { useInstanceId } from '@wordpress/compose'; import { useEffect, useRef, useState } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { plus } from '@wordpress/icons'; +import { LEFT, RIGHT } from '@wordpress/keycodes'; /** * Internal dependencies @@ -17,7 +18,6 @@ import { plus } from '@wordpress/icons'; import Button from '../button'; import ColorPicker from '../color-picker'; import Dropdown from '../dropdown'; -import KeyboardShortcuts from '../keyboard-shortcuts'; import { VisuallyHidden } from '../visually-hidden'; import { @@ -36,46 +36,11 @@ import { KEYBOARD_CONTROL_POINT_VARIATION, } from './constants'; -function ControlPointKeyboardMove( { value: position, onChange, children } ) { - const shortcuts = { - right( event ) { - // Stop propagation of the key press event to avoid focus moving - // to another editor area. - event.stopPropagation(); - const newPosition = clampPercent( - position + KEYBOARD_CONTROL_POINT_VARIATION - ); - onChange( newPosition ); - }, - left( event ) { - // Stop propagation of the key press event to avoid focus moving - // to another editor area. - event.stopPropagation(); - const newPosition = clampPercent( - position - KEYBOARD_CONTROL_POINT_VARIATION - ); - onChange( newPosition ); - }, - }; - - return ( - <KeyboardShortcuts shortcuts={ shortcuts }> - { children } - </KeyboardShortcuts> - ); -} - -function ControlPointButton( { - isOpen, - position, - color, - onChange, - ...additionalProps -} ) { +function ControlPointButton( { isOpen, position, color, ...additionalProps } ) { const instanceId = useInstanceId( ControlPointButton ); const descriptionId = `components-custom-gradient-picker__control-point-button-description-${ instanceId }`; return ( - <ControlPointKeyboardMove value={ position } onChange={ onChange }> + <> <Button aria-label={ sprintf( // translators: %1$s: gradient position e.g: 70, %2$s: gradient color code e.g: rgb(52,121,151). @@ -104,7 +69,7 @@ function ControlPointButton( { 'Use your left or right arrow keys or drag and drop with the mouse to change the gradient position. Press the button to change the color or remove the control point.' ) } </VisuallyHidden> - </ControlPointKeyboardMove> + </> ); } @@ -208,18 +173,40 @@ function ControlPoints( { ); } } } + onKeyDown={ ( event ) => { + if ( event.keyCode === LEFT ) { + // Stop propagation of the key press event to avoid focus moving + // to another editor area. + event.stopPropagation(); + onChange( + updateControlPointPosition( + controlPoints, + index, + clampPercent( + point.position - + KEYBOARD_CONTROL_POINT_VARIATION + ) + ) + ); + } else if ( event.keyCode === RIGHT ) { + // Stop propagation of the key press event to avoid focus moving + // to another editor area. + event.stopPropagation(); + onChange( + updateControlPointPosition( + controlPoints, + index, + clampPercent( + point.position + + KEYBOARD_CONTROL_POINT_VARIATION + ) + ) + ); + } + } } isOpen={ isOpen } position={ point.position } color={ point.color } - onChange={ ( newPosition ) => { - onChange( - updateControlPointPosition( - controlPoints, - index, - newPosition - ) - ); - } } /> ) } renderContent={ ( { onClose } ) => ( From da052f066b0f4f2a669a3144ebfeba757ab975dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <4710635+ellatrix@users.noreply.github.com> Date: Thu, 2 Sep 2021 20:07:52 +0300 Subject: [PATCH 131/214] Navigate regions: use React events for shortcuts (portal bubbles & contextual) (#33633) --- .../higher-order/navigate-regions/index.js | 98 ++++++++++++------- .../specs/editor/various/a11y.test.js | 2 +- .../edit-post/src/components/layout/index.js | 6 +- .../src/components/layout/interface.js | 6 +- .../components/interface-skeleton/index.js | 10 +- 5 files changed, 72 insertions(+), 50 deletions(-) diff --git a/packages/components/src/higher-order/navigate-regions/index.js b/packages/components/src/higher-order/navigate-regions/index.js index db9fc877e9467e..ca8cc47361d13c 100644 --- a/packages/components/src/higher-order/navigate-regions/index.js +++ b/packages/components/src/higher-order/navigate-regions/index.js @@ -1,19 +1,39 @@ /** * WordPress dependencies */ -import { useCallback, useState, useRef, useEffect } from '@wordpress/element'; +import { useState, useRef } from '@wordpress/element'; import { createHigherOrderComponent, - useKeyboardShortcut, + useRefEffect, + useMergeRefs, } from '@wordpress/compose'; -import { rawShortcut } from '@wordpress/keycodes'; +import { isKeyboardEvent } from '@wordpress/keycodes'; const defaultShortcuts = { - previous: [ 'ctrl+shift+`', rawShortcut.access( 'p' ) ], - next: [ 'ctrl+`', rawShortcut.access( 'n' ) ], + previous: [ + { + modifier: 'ctrlShift', + character: '`', + }, + { + modifier: 'access', + character: 'p', + }, + ], + next: [ + { + modifier: 'ctrl', + character: '`', + }, + { + modifier: 'access', + character: 'n', + }, + ], }; -export function useNavigateRegions( ref, shortcuts = defaultShortcuts ) { +export function useNavigateRegions( shortcuts = defaultShortcuts ) { + const ref = useRef(); const [ isFocusingRegions, setIsFocusingRegions ] = useState( false ); function focusRegion( offset ) { @@ -37,42 +57,48 @@ export function useNavigateRegions( ref, shortcuts = defaultShortcuts ) { nextRegion.focus(); setIsFocusingRegions( true ); } - const focusPrevious = useCallback( () => focusRegion( -1 ), [] ); - const focusNext = useCallback( () => focusRegion( 1 ), [] ); - useKeyboardShortcut( shortcuts.previous, focusPrevious, { - bindGlobal: true, - } ); - useKeyboardShortcut( shortcuts.next, focusNext, { bindGlobal: true } ); + const clickRef = useRefEffect( + ( element ) => { + function onClick() { + setIsFocusingRegions( false ); + } - useEffect( () => { - function onClick() { - setIsFocusingRegions( false ); - } - - ref.current.addEventListener( 'click', onClick ); + element.addEventListener( 'click', onClick ); - return () => { - ref.current?.removeEventListener( 'click', onClick ); - }; - }, [ setIsFocusingRegions ] ); + return () => { + element.removeEventListener( 'click', onClick ); + }; + }, + [ setIsFocusingRegions ] + ); - if ( ! isFocusingRegions ) { - return; - } - - return 'is-focusing-regions'; + return { + ref: useMergeRefs( [ ref, clickRef ] ), + className: isFocusingRegions ? 'is-focusing-regions' : '', + onKeyDown( event ) { + if ( + shortcuts.previous.some( ( { modifier, character } ) => { + return isKeyboardEvent[ modifier ]( event, character ); + } ) + ) { + focusRegion( -1 ); + } else if ( + shortcuts.next.some( ( { modifier, character } ) => { + return isKeyboardEvent[ modifier ]( event, character ); + } ) + ) { + focusRegion( 1 ); + } + }, + }; } export default createHigherOrderComponent( - ( Component ) => ( { shortcuts, ...props } ) => { - const ref = useRef(); - const className = useNavigateRegions( ref, shortcuts ); - return ( - <div ref={ ref } className={ className }> - <Component { ...props } /> - </div> - ); - }, + ( Component ) => ( { shortcuts, ...props } ) => ( + <div { ...useNavigateRegions( shortcuts ) }> + <Component { ...props } /> + </div> + ), 'navigateRegions' ); diff --git a/packages/e2e-tests/specs/editor/various/a11y.test.js b/packages/e2e-tests/specs/editor/various/a11y.test.js index a25b5d9760c3ce..1c7e9fe76b4c98 100644 --- a/packages/e2e-tests/specs/editor/various/a11y.test.js +++ b/packages/e2e-tests/specs/editor/various/a11y.test.js @@ -15,7 +15,7 @@ describe( 'a11y', () => { } ); it( 'tabs header bar', async () => { - await pressKeyWithModifier( 'ctrl', '~' ); + await pressKeyWithModifier( 'ctrl', '`' ); await page.keyboard.press( 'Tab' ); diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 51311d59ea7b6e..202f9648d95179 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -112,12 +112,10 @@ function Layout( { styles } ) { hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(), previousShortcut: select( keyboardShortcutsStore - ).getAllShortcutRawKeyCombinations( - 'core/edit-post/previous-region' - ), + ).getAllShortcutKeyCombinations( 'core/edit-post/previous-region' ), nextShortcut: select( keyboardShortcutsStore - ).getAllShortcutRawKeyCombinations( 'core/edit-post/next-region' ), + ).getAllShortcutKeyCombinations( 'core/edit-post/next-region' ), showIconLabels: select( editPostStore ).isFeatureActive( 'showIconLabels' ), diff --git a/packages/edit-widgets/src/components/layout/interface.js b/packages/edit-widgets/src/components/layout/interface.js index 0dbc7bb404e028..0969f0dabf9c1a 100644 --- a/packages/edit-widgets/src/components/layout/interface.js +++ b/packages/edit-widgets/src/components/layout/interface.js @@ -65,14 +65,12 @@ function Interface( { blockEditorSettings } ) { ).isFeatureActive( 'core/edit-widgets', 'showBlockBreadcrumbs' ), previousShortcut: select( keyboardShortcutsStore - ).getAllShortcutRawKeyCombinations( + ).getAllShortcutKeyCombinations( 'core/edit-widgets/previous-region' ), nextShortcut: select( keyboardShortcutsStore - ).getAllShortcutRawKeyCombinations( - 'core/edit-widgets/next-region' - ), + ).getAllShortcutKeyCombinations( 'core/edit-widgets/next-region' ), } ), [] ); diff --git a/packages/interface/src/components/interface-skeleton/index.js b/packages/interface/src/components/interface-skeleton/index.js index 26bf20681f659a..1f4c020dd7cf8f 100644 --- a/packages/interface/src/components/interface-skeleton/index.js +++ b/packages/interface/src/components/interface-skeleton/index.js @@ -9,7 +9,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { forwardRef, useEffect, useRef } from '@wordpress/element'; +import { forwardRef, useEffect } from '@wordpress/element'; import { __unstableUseNavigateRegions as useNavigateRegions } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useMergeRefs } from '@wordpress/compose'; @@ -44,8 +44,7 @@ function InterfaceSkeleton( }, ref ) { - const fallbackRef = useRef(); - const regionsClassName = useNavigateRegions( fallbackRef, shortcuts ); + const navigateRegionsProps = useNavigateRegions( shortcuts ); useHTMLClass( 'interface-interface-skeleton__html-container' ); @@ -70,11 +69,12 @@ function InterfaceSkeleton( return ( <div - ref={ useMergeRefs( [ ref, fallbackRef ] ) } + { ...navigateRegionsProps } + ref={ useMergeRefs( [ ref, navigateRegionsProps.ref ] ) } className={ classnames( className, 'interface-interface-skeleton', - regionsClassName, + navigateRegionsProps.className, !! footer && 'has-footer' ) } > From 32a370f5701bfc6cd1eda1c3fc6c298e115508ad Mon Sep 17 00:00:00 2001 From: Glen Davies <glendaviesnz@users.noreply.github.com> Date: Fri, 3 Sep 2021 09:44:48 +1200 Subject: [PATCH 132/214] Gallery block: fix bug with stalled upload when image size too large (#34371) * Flag isTemp to false on upload error to prevent the isTemp effect running again and showing stalled upload Co-authored-by: Glen Davies <glen.davies@a8c.com> --- packages/block-library/src/gallery/editor.scss | 17 ++++++++++++++++- .../block-library/src/gallery/use-get-media.js | 13 ++++--------- packages/block-library/src/image/edit.js | 3 ++- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index 32b2b39405cf86..584654a814af11 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -16,9 +16,24 @@ figure.wp-block-gallery { flex: 0 0 100%; } - .components-form-file-upload { + > .components-form-file-upload { flex-basis: 100%; } + + .wp-block-image { + .components-notice.is-error { + display: block; + } + .components-notice__content { + margin: 4px 0; + } + .components-notice__dismiss { + position: absolute; + top: 0; + right: 5px; + } + } + // @todo: this deserves a refactor, by being moved to the toolbar. .block-editor-media-placeholder.is-appender { .components-placeholder__label { diff --git a/packages/block-library/src/gallery/use-get-media.js b/packages/block-library/src/gallery/use-get-media.js index 597b112a8af3d2..1bf33c767a4c64 100644 --- a/packages/block-library/src/gallery/use-get-media.js +++ b/packages/block-library/src/gallery/use-get-media.js @@ -10,18 +10,13 @@ export default function useGetMedia( innerBlockImages ) { const imageMedia = useSelect( ( select ) => { - if ( - ! innerBlockImages?.length || - innerBlockImages.some( - ( imageBlock ) => ! imageBlock.attributes.id - ) - ) { + if ( ! innerBlockImages?.length ) { return currentImageMedia; } - const imageIds = innerBlockImages.map( - ( imageBlock ) => imageBlock.attributes.id - ); + const imageIds = innerBlockImages + .map( ( imageBlock ) => imageBlock.attributes.id ) + .filter( ( id ) => id !== undefined ); if ( imageIds.length === 0 ) { return currentImageMedia; diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 3a02291ea0c954..7f29a8d1078c51 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -246,7 +246,7 @@ export function ImageEdit( { } ); } - const isTemp = isTemporaryImage( id, url ); + let isTemp = isTemporaryImage( id, url ); // Upload a temporary image on mount. useEffect( () => { @@ -264,6 +264,7 @@ export function ImageEdit( { }, allowedTypes: ALLOWED_MEDIA_TYPES, onError: ( message ) => { + isTemp = false; noticeOperations.createErrorNotice( message ); setAttributes( { src: undefined, From f0ee75922094f4d96cd72947970da4f956af61dd Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 3 Sep 2021 09:35:35 +1000 Subject: [PATCH 133/214] Block Support: Add gap block support feature (#33991) * Add gap block support feature based on #32571 * Server-render the gap block support, and skip serialization on save in the editor * Add PHP tests for spacing gap support server-side rendering * Rename support from customBlockGap to blockGap * Align whitespace --- lib/block-supports/spacing.php | 53 +++++++ lib/class-wp-theme-json-gutenberg.php | 1 + lib/theme.json | 1 + packages/block-editor/src/hooks/dimensions.js | 29 +++- packages/block-editor/src/hooks/gap.js | 128 ++++++++++++++++ packages/block-editor/src/hooks/style.js | 49 +++++- packages/block-editor/src/hooks/test/style.js | 4 + packages/blocks/src/api/constants.js | 1 + .../components/sidebar/dimensions-panel.js | 37 ++++- phpunit/block-supports/spacing-test.php | 142 ++++++++++++++++++ 10 files changed, 435 insertions(+), 10 deletions(-) create mode 100644 packages/block-editor/src/hooks/gap.js create mode 100644 phpunit/block-supports/spacing-test.php diff --git a/lib/block-supports/spacing.php b/lib/block-supports/spacing.php index 78f5b59f90fb04..ca7b77f43864b9 100644 --- a/lib/block-supports/spacing.php +++ b/lib/block-supports/spacing.php @@ -90,6 +90,57 @@ function gutenberg_skip_spacing_serialization( $block_type ) { $spacing_support['__experimentalSkipSerialization']; } + +/** + * Renders the spacing gap support to the block wrapper, to ensure + * that the CSS variable is rendered in all environments. + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. + */ +function gutenberg_render_spacing_gap_support( $block_content, $block ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + $has_gap_support = gutenberg_block_has_support( $block_type, array( 'spacing', 'blockGap' ), false ); + if ( ! $has_gap_support || ! isset( $block['attrs']['style']['spacing']['blockGap'] ) ) { + return $block_content; + } + + $gap_value = $block['attrs']['style']['spacing']['blockGap']; + + // Skip if gap value contains unsupported characters. + // Regex for CSS value borrowed from `safecss_filter_attr`, and used here + // because we only want to match against the value, not the CSS attribute. + if ( preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ) { + return $block_content; + } + + $style = sprintf( + '--wp--style--block-gap: %s', + esc_attr( $gap_value ) + ); + + // Attempt to update an existing style attribute on the wrapper element. + $injected_style = preg_replace( + '/^([^>.]+?)(' . preg_quote( 'style="', '/' ) . ')(?=.+?>)/', + '$1$2' . $style . '; ', + $block_content, + 1 + ); + + // If there is no existing style attribute, add one to the wrapper element. + if ( $injected_style === $block_content ) { + $injected_style = preg_replace( + '/<([a-zA-Z0-9]+)([ >])/', + '<$1 style="' . $style . '"$2', + $block_content, + 1 + ); + }; + + return $injected_style; +} + // Register the block support. WP_Block_Supports::get_instance()->register( 'spacing', @@ -98,3 +149,5 @@ function gutenberg_skip_spacing_serialization( $block_type ) { 'apply' => 'gutenberg_apply_spacing_support', ) ); + +add_filter( 'render_block', 'gutenberg_render_spacing_gap_support', 10, 2 ); diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index ee12dad41fa089..474294caa2a218 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -99,6 +99,7 @@ class WP_Theme_JSON_Gutenberg { 'wideSize' => null, ), 'spacing' => array( + 'blockGap' => null, 'customMargin' => null, 'customPadding' => null, 'units' => null, diff --git a/lib/theme.json b/lib/theme.json index 6b52969009beed..5d952b381ef932 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -211,6 +211,7 @@ ] }, "spacing": { + "blockGap": false, "customMargin": false, "customPadding": false, "units": [ "px", "em", "rem", "vh", "vw", "%" ] diff --git a/packages/block-editor/src/hooks/dimensions.js b/packages/block-editor/src/hooks/dimensions.js index 697955670b497e..d85ce67104b391 100644 --- a/packages/block-editor/src/hooks/dimensions.js +++ b/packages/block-editor/src/hooks/dimensions.js @@ -13,6 +13,13 @@ import { getBlockSupport } from '@wordpress/blocks'; * Internal dependencies */ import InspectorControls from '../components/inspector-controls'; +import { + GapEdit, + hasGapSupport, + hasGapValue, + resetGap, + useIsGapDisabled, +} from './gap'; import { MarginEdit, hasMarginSupport, @@ -41,6 +48,7 @@ export const AXIAL_SIDES = [ 'vertical', 'horizontal' ]; * @return {WPElement} Inspector controls for spacing support features. */ export function DimensionsPanel( props ) { + const isGapDisabled = useIsGapDisabled( props ); const isPaddingDisabled = useIsPaddingDisabled( props ); const isMarginDisabled = useIsMarginDisabled( props ); const isDisabled = useIsDimensionsDisabled( props ); @@ -64,6 +72,7 @@ export function DimensionsPanel( props ) { ...style, spacing: { ...style?.spacing, + blockGap: undefined, margin: undefined, padding: undefined, }, @@ -98,6 +107,17 @@ export function DimensionsPanel( props ) { <MarginEdit { ...props } /> </ToolsPanelItem> ) } + { ! isGapDisabled && ( + <ToolsPanelItem + className="single-column" + hasValue={ () => hasGapValue( props ) } + label={ __( 'Block gap' ) } + onDeselect={ () => resetGap( props ) } + isShownByDefault={ defaultSpacingControls?.blockGap } + > + <GapEdit { ...props } /> + </ToolsPanelItem> + ) } </ToolsPanel> </InspectorControls> ); @@ -115,7 +135,11 @@ export function hasDimensionsSupport( blockName ) { return false; } - return hasPaddingSupport( blockName ) || hasMarginSupport( blockName ); + return ( + hasGapSupport( blockName ) || + hasPaddingSupport( blockName ) || + hasMarginSupport( blockName ) + ); } /** @@ -126,10 +150,11 @@ export function hasDimensionsSupport( blockName ) { * @return {boolean} If spacing support is completely disabled. */ const useIsDimensionsDisabled = ( props = {} ) => { + const gapDisabled = useIsGapDisabled( props ); const paddingDisabled = useIsPaddingDisabled( props ); const marginDisabled = useIsMarginDisabled( props ); - return paddingDisabled && marginDisabled; + return gapDisabled && paddingDisabled && marginDisabled; }; /** diff --git a/packages/block-editor/src/hooks/gap.js b/packages/block-editor/src/hooks/gap.js new file mode 100644 index 00000000000000..d20b94c2b72cd6 --- /dev/null +++ b/packages/block-editor/src/hooks/gap.js @@ -0,0 +1,128 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Platform } from '@wordpress/element'; +import { getBlockSupport } from '@wordpress/blocks'; +import { + __experimentalUseCustomUnits as useCustomUnits, + __experimentalUnitControl as UnitControl, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import useSetting from '../components/use-setting'; +import { SPACING_SUPPORT_KEY } from './dimensions'; +import { cleanEmptyObject } from './utils'; + +/** + * Determines if there is gap support. + * + * @param {string|Object} blockType Block name or Block Type object. + * @return {boolean} Whether there is support. + */ +export function hasGapSupport( blockType ) { + const support = getBlockSupport( blockType, SPACING_SUPPORT_KEY ); + return !! ( true === support || support?.blockGap ); +} + +/** + * Checks if there is a current value in the gap block support attributes. + * + * @param {Object} props Block props. + * @return {boolean} Whether or not the block has a gap value set. + */ +export function hasGapValue( props ) { + return props.attributes.style?.spacing?.blockGap !== undefined; +} + +/** + * Resets the gap block support attribute. This can be used when disabling + * the gap support controls for a block via a progressive discovery panel. + * + * @param {Object} props Block props. + * @param {Object} props.attributes Block's attributes. + * @param {Object} props.setAttributes Function to set block's attributes. + */ +export function resetGap( { attributes = {}, setAttributes } ) { + const { style } = attributes; + + setAttributes( { + style: { + ...style, + spacing: { + ...style?.spacing, + blockGap: undefined, + }, + }, + } ); +} + +/** + * Custom hook that checks if gap settings have been disabled. + * + * @param {string} name The name of the block. + * @return {boolean} Whether the gap setting is disabled. + */ +export function useIsGapDisabled( { name: blockName } = {} ) { + const isDisabled = ! useSetting( 'spacing.blockGap' ); + return ! hasGapSupport( blockName ) || isDisabled; +} + +/** + * Inspector control panel containing the gap related configuration + * + * @param {Object} props + * + * @return {WPElement} Gap edit element. + */ +export function GapEdit( props ) { + const { + attributes: { style }, + setAttributes, + } = props; + + const units = useCustomUnits( { + availableUnits: useSetting( 'spacing.units' ) || [ + '%', + 'px', + 'em', + 'rem', + 'vw', + ], + } ); + + if ( useIsGapDisabled( props ) ) { + return null; + } + + const onChange = ( next ) => { + const newStyle = { + ...style, + spacing: { + ...style?.spacing, + blockGap: next, + }, + }; + + setAttributes( { + style: cleanEmptyObject( newStyle ), + } ); + }; + + return Platform.select( { + web: ( + <> + <UnitControl + label={ __( 'Block gap' ) } + min={ 0 } + onChange={ onChange } + units={ units } + value={ style?.spacing?.blockGap } + /> + </> + ), + native: null, + } ); +} diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index 87bf4685d50d42..9bc7c0e5997c8f 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -144,7 +144,14 @@ function addAttribute( settings ) { return settings; } -const skipSerializationPaths = { +/** + * A dictionary of paths to flag skipping block support serialization as the key, + * with values providing the style paths to be omitted from serialization. + * + * @constant + * @type {Record<string, string[]>} + */ +const skipSerializationPathsEdit = { [ `${ BORDER_SUPPORT_KEY }.__experimentalSkipSerialization` ]: [ 'border' ], [ `${ COLOR_SUPPORT_KEY }.__experimentalSkipSerialization` ]: [ COLOR_SUPPORT_KEY, @@ -157,23 +164,46 @@ const skipSerializationPaths = { ], }; +/** + * A dictionary of paths to flag skipping block support serialization as the key, + * with values providing the style paths to be omitted from serialization. + * + * Extends the Edit skip paths to enable skipping additional paths in just + * the Save component. This allows a block support to be serialized within the + * editor, while using an alternate approach, such as server-side rendering, when + * the support is saved. + * + * @constant + * @type {Record<string, string[]>} + */ +const skipSerializationPathsSave = { + ...skipSerializationPathsEdit, + [ `${ SPACING_SUPPORT_KEY }` ]: [ 'spacing.blockGap' ], +}; + /** * Override props assigned to save component to inject the CSS variables definition. * - * @param {Object} props Additional props applied to save element. - * @param {Object} blockType Block type. - * @param {Object} attributes Block attributes. + * @param {Object} props Additional props applied to save element. + * @param {Object} blockType Block type. + * @param {Object} attributes Block attributes. + * @param {?Record<string, string[]>} skipPaths An object of keys and paths to skip serialization. * * @return {Object} Filtered props applied to save element. */ -export function addSaveProps( props, blockType, attributes ) { +export function addSaveProps( + props, + blockType, + attributes, + skipPaths = skipSerializationPathsSave +) { if ( ! hasStyleSupport( blockType ) ) { return props; } let { style } = attributes; - forEach( skipSerializationPaths, ( path, indicator ) => { + forEach( skipPaths, ( path, indicator ) => { if ( getBlockSupport( blockType, indicator ) ) { style = omit( style, path ); } @@ -207,7 +237,12 @@ export function addEditProps( settings ) { props = existingGetEditWrapperProps( attributes ); } - return addSaveProps( props, settings, attributes ); + return addSaveProps( + props, + settings, + attributes, + skipSerializationPathsEdit + ); }; return settings; diff --git a/packages/block-editor/src/hooks/test/style.js b/packages/block-editor/src/hooks/test/style.js index 706d9a93ed0ba4..e8c3264eeba6b1 100644 --- a/packages/block-editor/src/hooks/test/style.js +++ b/packages/block-editor/src/hooks/test/style.js @@ -24,11 +24,13 @@ describe( 'getInlineStyles', () => { color: '#21759b', }, spacing: { + blockGap: '1em', padding: { top: '10px' }, margin: { bottom: '15px' }, }, } ) ).toEqual( { + '--wp--style--block-gap': '1em', backgroundColor: 'black', borderColor: '#21759b', borderRadius: '10px', @@ -96,11 +98,13 @@ describe( 'getInlineStyles', () => { expect( getInlineStyles( { spacing: { + blockGap: '1em', margin: '10px', padding: '20px', }, } ) ).toEqual( { + '--wp--style--block-gap': '1em', margin: '10px', padding: '20px', } ); diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index e60d868fc2227b..ff8cf6fdb94249 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -114,6 +114,7 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, '--wp--style--block-gap': { value: [ 'spacing', 'blockGap' ], + support: [ 'spacing', 'blockGap' ], }, }; diff --git a/packages/edit-site/src/components/sidebar/dimensions-panel.js b/packages/edit-site/src/components/sidebar/dimensions-panel.js index 05b4aadd2d5bf0..72059768b30189 100644 --- a/packages/edit-site/src/components/sidebar/dimensions-panel.js +++ b/packages/edit-site/src/components/sidebar/dimensions-panel.js @@ -6,6 +6,7 @@ import { __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, __experimentalBoxControl as BoxControl, + __experimentalUnitControl as UnitControl, __experimentalUseCustomUnits as useCustomUnits, } from '@wordpress/components'; import { __experimentalUseCustomSides as useCustomSides } from '@wordpress/block-editor'; @@ -20,8 +21,9 @@ const AXIAL_SIDES = [ 'horizontal', 'vertical' ]; export function useHasDimensionsPanel( context ) { const hasPadding = useHasPadding( context ); const hasMargin = useHasMargin( context ); + const hasGap = useHasGap( context ); - return hasPadding || hasMargin; + return hasPadding || hasMargin || hasGap; } function useHasPadding( { name, supports } ) { @@ -36,6 +38,12 @@ function useHasMargin( { name, supports } ) { return settings && supports.includes( 'margin' ); } +function useHasGap( { name, supports } ) { + const settings = useSetting( 'spacing.blockGap', name ); + + return settings && supports.includes( '--wp--style--block-gap' ); +} + function filterValuesBySides( values, sides ) { if ( ! sides ) { // If no custom side configuration all sides are opted into by default. @@ -78,6 +86,7 @@ export default function DimensionsPanel( { context, getStyle, setStyle } ) { const { name } = context; const showPaddingControl = useHasPadding( context ); const showMarginControl = useHasMargin( context ); + const showGapControl = useHasGap( context ); const units = useCustomUnits( { availableUnits: useSetting( 'spacing.units', name ) || [ '%', @@ -116,9 +125,18 @@ export default function DimensionsPanel( { context, getStyle, setStyle } ) { const hasMarginValue = () => marginValues && Object.keys( marginValues ).length; + const gapValue = getStyle( name, '--wp--style--block-gap' ); + + const setGapValue = ( newGapValue ) => { + setStyle( name, '--wp--style--block-gap', newGapValue ); + }; + const resetGapValue = () => setGapValue( undefined ); + const hasGapValue = () => !! gapValue; + const resetAll = () => { resetPaddingValue(); resetMarginValue(); + resetGapValue(); }; return ( @@ -163,6 +181,23 @@ export default function DimensionsPanel( { context, getStyle, setStyle } ) { /> </ToolsPanelItem> ) } + { showGapControl && ( + <ToolsPanelItem + className="single-column" + hasValue={ hasGapValue } + label={ __( 'Block gap' ) } + onDeselect={ resetGapValue } + isShownByDefault={ true } + > + <UnitControl + label={ __( 'Block gap' ) } + min={ 0 } + onChange={ setGapValue } + units={ units } + value={ gapValue } + /> + </ToolsPanelItem> + ) } </ToolsPanel> ); } diff --git a/phpunit/block-supports/spacing-test.php b/phpunit/block-supports/spacing-test.php new file mode 100644 index 00000000000000..1038cfcb5ca97d --- /dev/null +++ b/phpunit/block-supports/spacing-test.php @@ -0,0 +1,142 @@ +<?php + +/** + * Test the Spacing block supports. + * + * @package Gutenberg + */ + +class WP_Block_Supports_Spacing_Test extends WP_UnitTestCase { + private $sample_block_content = '<div class="wp-block-test-block">Test</div>'; + private $test_gap_block_value = array(); + private $test_gap_block_args = array(); + + function setUp() { + parent::setUp(); + + $this->test_gap_block_value = array( + 'blockName' => 'test/test-block', + 'attrs' => array( + 'style' => array( + 'spacing' => array( + 'blockGap' => '3em', + ), + ), + ), + ); + + $this->test_gap_block_args = array( + 'api_version' => 2, + 'supports' => array( + 'spacing' => array( + 'blockGap' => true, + ), + ), + ); + } + + function tearDown() { + unregister_block_type( 'test/test-block' ); + + parent::tearDown(); + } + + function test_spacing_gap_block_support_renders_block_inline_style() { + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = gutenberg_render_spacing_gap_support( + $this->sample_block_content, + $this->test_gap_block_value + ); + + $this->assertSame( + '<div style="--wp--style--block-gap: 3em" class="wp-block-test-block">Test</div>', + $render_output + ); + } + + function test_spacing_gap_block_support_renders_block_inline_style_with_inner_tag() { + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = gutenberg_render_spacing_gap_support( + '<div class="wp-test-block"><p style="color: red;">Test</p></div>', + $this->test_gap_block_value + ); + + $this->assertSame( + '<div style="--wp--style--block-gap: 3em" class="wp-test-block"><p style="color: red;">Test</p></div>', + $render_output + ); + } + + function test_spacing_gap_block_support_renders_block_inline_style_with_no_other_attributes() { + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = gutenberg_render_spacing_gap_support( + '<div><p>Test</p></div>', + $this->test_gap_block_value + ); + + $this->assertSame( + '<div style="--wp--style--block-gap: 3em"><p>Test</p></div>', + $render_output + ); + } + + function test_spacing_gap_block_support_renders_appended_block_inline_style() { + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = gutenberg_render_spacing_gap_support( + '<div class="wp-test-block" style="background: green;"><p style="color: red;">Test</p></div>', + $this->test_gap_block_value + ); + + $this->assertSame( + '<div class="wp-test-block" style="--wp--style--block-gap: 3em; background: green;"><p style="color: red;">Test</p></div>', + $render_output + ); + } + + function test_spacing_gap_block_support_does_not_render_style_when_support_is_false() { + $this->test_gap_block_args['supports']['spacing']['blockGap'] = false; + + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = gutenberg_render_spacing_gap_support( + $this->sample_block_content, + $this->test_gap_block_value + ); + + $this->assertEquals( + $this->sample_block_content, + $render_output + ); + } + + function test_spacing_gap_block_support_does_not_render_style_when_gap_is_null() { + $this->test_gap_block_value['attrs']['style']['spacing']['blockGap'] = null; + $this->test_gap_block_args['supports']['spacing']['blockGap'] = true; + + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = gutenberg_render_spacing_gap_support( + $this->sample_block_content, + $this->test_gap_block_value + ); + + $this->assertEquals( + $this->sample_block_content, + $render_output + ); + } + + function test_spacing_gap_block_support_does_not_render_style_when_gap_is_illegal_value() { + $this->test_gap_block_value['attrs']['style']['spacing']['blockGap'] = '" javascript="alert("hello");'; + $this->test_gap_block_args['supports']['spacing']['blockGap'] = true; + + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = gutenberg_render_spacing_gap_support( + $this->sample_block_content, + $this->test_gap_block_value + ); + + $this->assertEquals( + $this->sample_block_content, + $render_output + ); + } +} From 4fd82ebce3e235ec8b542d30285b45d7a78883cb Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 3 Sep 2021 12:03:59 +1000 Subject: [PATCH 134/214] Group letter spacing correctly under typography supports (#34515) --- packages/block-editor/src/hooks/letter-spacing.js | 3 ++- packages/blocks/src/api/constants.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/hooks/letter-spacing.js b/packages/block-editor/src/hooks/letter-spacing.js index 6de6193fe4d814..25369ae9daaa81 100644 --- a/packages/block-editor/src/hooks/letter-spacing.js +++ b/packages/block-editor/src/hooks/letter-spacing.js @@ -14,7 +14,8 @@ import { cleanEmptyObject } from './utils'; * Key within block settings' supports array indicating support for letter-spacing * e.g. settings found in `block.json`. */ -export const LETTER_SPACING_SUPPORT_KEY = '__experimentalLetterSpacing'; +export const LETTER_SPACING_SUPPORT_KEY = + 'typography.__experimentalLetterSpacing'; /** * Inspector control panel containing the letter-spacing options. diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index ff8cf6fdb94249..2ca489dd609f51 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -110,7 +110,7 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, letterSpacing: { value: [ 'typography', 'letterSpacing' ], - support: [ '__experimentalLetterSpacing' ], + support: [ 'typography', '__experimentalLetterSpacing' ], }, '--wp--style--block-gap': { value: [ 'spacing', 'blockGap' ], From e5139831c34ed893ca5a42c146cedfe340d5a979 Mon Sep 17 00:00:00 2001 From: annezazu <annezazu@gmail.com> Date: Thu, 2 Sep 2021 20:18:46 -0600 Subject: [PATCH 135/214] Update Site Logo block description to be concise (#34471) * Update Site Logo block description to be concise This minor update simplifies the language used to make it more concise while still attempting to get the right messaging across, including education pieces around the site icon. * Made language even more concise * Add in educational context to the site logo description Since there isn't an option to add a site icon in FSE, it's important to add extra context about what this is vs what it isn't. --- packages/block-library/src/site-logo/block.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/site-logo/block.json b/packages/block-library/src/site-logo/block.json index c40cfcdd2bab06..b2adec6911dc58 100644 --- a/packages/block-library/src/site-logo/block.json +++ b/packages/block-library/src/site-logo/block.json @@ -3,7 +3,7 @@ "name": "core/site-logo", "title": "Site Logo", "category": "layout", - "description": "Useful for displaying a graphic mark, design, or symbol to represent the site. Once a site logo is set, it can be reused in different places and templates. It should not be confused with the site icon, which is the small image used in the dashboard, browser tabs, public search results, etc, to help recognize a site.", + "description": "Display a graphic to represent this site. Update the block, and the changes apply everywhere it’s used. This is different than the site icon, which is the smaller image visible in your dashboard, browser tabs, etc used to help others recognize this site.", "textdomain": "default", "attributes": { "align": { From 350d7e202de7ec148211049eafa2d7a4050cc1ff Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 3 Sep 2021 12:22:56 +1000 Subject: [PATCH 136/214] Fix check for border support panel in global styles (#34516) --- packages/edit-site/src/components/sidebar/border-panel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/sidebar/border-panel.js b/packages/edit-site/src/components/sidebar/border-panel.js index e7d5c801ff9355..46f8dbad535049 100644 --- a/packages/edit-site/src/components/sidebar/border-panel.js +++ b/packages/edit-site/src/components/sidebar/border-panel.js @@ -32,7 +32,7 @@ export function useHasBorderPanel( { supports, name } ) { useHasBorderWidthControl( { supports, name } ), ]; - return controls.every( Boolean ); + return controls.some( Boolean ); } function useHasBorderColorControl( { supports, name } ) { From 5b986d0d5d7b1becb42b442332492f0fc9d62a6c Mon Sep 17 00:00:00 2001 From: Jason Johnston <jhnstn@users.noreply.github.com> Date: Thu, 2 Sep 2021 23:26:13 -0400 Subject: [PATCH 137/214] [RNMobile] Set a min height on the inserter menu (#34513) * Only use flexbox for fullscreen inserter * Revert "Only use flexbox for fullscreen inserter" This reverts commit 12274c6cb9dd1f9105d74ff5a972cbfbd7ed248a. * Set a min height on the inserter menu Co-authored-by: jhnstn <jason@readysetandco.com> --- packages/block-editor/src/components/inserter/menu.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js index 82c8438421dcda..8b5f2b1919705e 100644 --- a/packages/block-editor/src/components/inserter/menu.native.js +++ b/packages/block-editor/src/components/inserter/menu.native.js @@ -201,7 +201,7 @@ function InserterMenu( { </> } hasNavigation - setMinHeightToMaxHeight={ showSearchForm } + setMinHeightToMaxHeight={ true } contentStyle={ styles[ 'inserter-menu__list' ] } isFullScreen={ ! isIOS && showSearchForm } allowDragIndicator={ true } From 7de47a28dc2476894152ff25e3a86879e85042c9 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 3 Sep 2021 16:26:58 +1000 Subject: [PATCH 138/214] MenuItem: Add right padding for unchecked radio and checkbox items (#34406) * MenuItem: Add right padding for unchecked radio and checkbox items * Add comment explaining the reason for the padding value --- packages/components/src/menu-item/style.scss | 9 +++++++ .../src/tools-panel/stories/index.js | 26 ++++++++++++++----- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/packages/components/src/menu-item/style.scss b/packages/components/src/menu-item/style.scss index 0f5badd4ae3551..80f97da381947e 100644 --- a/packages/components/src/menu-item/style.scss +++ b/packages/components/src/menu-item/style.scss @@ -2,6 +2,15 @@ .components-menu-item__button.components-button { width: 100%; + &[role="menuitemradio"], + &[role="menuitemcheckbox"] { + .components-menu-item__item:only-child { + // Ensure unchecked items have clearance for consistency + // with checked items containing an icon or shortcut. + padding-right: $grid-unit-60; + } + } + .components-menu-items__item-icon { margin-right: -2px; // This optically balances the icon. margin-left: $grid-unit-30; diff --git a/packages/components/src/tools-panel/stories/index.js b/packages/components/src/tools-panel/stories/index.js index 3f40051135857a..407b356089ed50 100644 --- a/packages/components/src/tools-panel/stories/index.js +++ b/packages/components/src/tools-panel/stories/index.js @@ -22,6 +22,7 @@ export default { export const _default = () => { const [ height, setHeight ] = useState(); + const [ minHeight, setMinHeight ] = useState(); const [ width, setWidth ] = useState(); const resetAll = () => { @@ -37,6 +38,18 @@ export const _default = () => { label="Display options" resetAll={ resetAll } > + <ToolsPanelItem + className="single-column" + hasValue={ () => !! width } + label="Width" + onDeselect={ () => setWidth( undefined ) } + > + <UnitControl + label="Width" + value={ width } + onChange={ ( next ) => setWidth( next ) } + /> + </ToolsPanelItem> <ToolsPanelItem className="single-column" hasValue={ () => !! height } @@ -50,15 +63,14 @@ export const _default = () => { /> </ToolsPanelItem> <ToolsPanelItem - className="single-column" - hasValue={ () => !! width } - label="Width" - onDeselect={ () => setWidth( undefined ) } + hasValue={ () => !! minHeight } + label="Minimum height" + onDeselect={ () => setMinHeight( undefined ) } > <UnitControl - label="Width" - value={ width } - onChange={ ( next ) => setWidth( next ) } + label="Minimum height" + value={ minHeight } + onChange={ ( next ) => setMinHeight( next ) } /> </ToolsPanelItem> </ToolsPanel> From 96b54059fbc717daaca7dcddd4e7548c50275e5d Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 3 Sep 2021 16:41:50 +1000 Subject: [PATCH 139/214] Font Appearance Control: Fix error in global styles for Site Title in TT1-Blocks (#34520) * Font Appearance Control: Fix error in global styles for Site Title in TT1-Blocks --- .../src/components/font-appearance-control/index.js | 4 ++++ packages/components/src/custom-select-control/index.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/font-appearance-control/index.js b/packages/block-editor/src/components/font-appearance-control/index.js index 80b3b24357d660..711ded6b70835b 100644 --- a/packages/block-editor/src/components/font-appearance-control/index.js +++ b/packages/block-editor/src/components/font-appearance-control/index.js @@ -165,6 +165,10 @@ export default function FontAppearanceControl( props ) { // Adjusts screen reader description based on styles or weights. const getDescribedBy = () => { + if ( ! currentSelection ) { + return __( 'No selected font appearance' ); + } + if ( ! hasFontStyles ) { return sprintf( // translators: %s: Currently selected font weight. diff --git a/packages/components/src/custom-select-control/index.js b/packages/components/src/custom-select-control/index.js index b1498938ab8bf4..560ac128218a34 100644 --- a/packages/components/src/custom-select-control/index.js +++ b/packages/components/src/custom-select-control/index.js @@ -84,7 +84,7 @@ export default function CustomSelectControl( { const controlDescribedBy = describedBy ? describedBy : // translators: %s: The selected option. - sprintf( __( 'Currently selected: %s' ), selectedItem.name ); + sprintf( __( 'Currently selected: %s' ), selectedItem?.name ); const menuProps = getMenuProps( { className: 'components-custom-select-control__menu', From eeb122b7f82137d4114a4708201565c922539361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <4710635+ellatrix@users.noreply.github.com> Date: Fri, 3 Sep 2021 10:00:45 +0300 Subject: [PATCH 140/214] Rich text (core): onFocus method can be replaced with HTMLElement.focus (#32054) --- packages/block-editor/src/components/rich-text/index.js | 6 +++++- packages/rich-text/src/component/index.js | 9 +-------- .../rich-text/src/component/use-input-and-selection.js | 8 +++++++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index fe4f20cf3d41d1..7ea9832740e4e9 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -224,7 +224,7 @@ function RichTextWrapper( ); } - const { value, onChange, onFocus, ref: richTextRef } = useRichText( { + const { value, onChange, ref: richTextRef } = useRichText( { value: adjustedValue, onChange( html, { __unstableFormats, __unstableText } ) { adjustedOnChange( html ); @@ -297,6 +297,10 @@ function RichTextWrapper( } } + function onFocus() { + anchorRef.current.focus(); + } + const TagName = tagName; const content = ( <> diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index b10b509ef3b53f..b5c4f081490e57 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -120,6 +120,7 @@ export function useRichText( { * @param {Object} newRecord The record to sync and apply. */ function handleChange( newRecord ) { + record.current = newRecord; applyRecord( newRecord ); if ( disableFormats ) { @@ -137,8 +138,6 @@ export function useRichText( { } ); } - record.current = newRecord; - const { start, end, formats, text } = newRecord; // Selection must be updated first, so it is recorded in history when @@ -178,11 +177,6 @@ export function useRichText( { hadSelectionUpdate.current = false; }, [ hadSelectionUpdate.current ] ); - function focus() { - ref.current.focus(); - applyRecord( record.current ); - } - const mergedRefs = useMergeRefs( [ ref, useDefaultStyle(), @@ -218,7 +212,6 @@ export function useRichText( { return { value: record.current, onChange: handleChange, - onFocus: focus, ref: mergedRefs, }; } diff --git a/packages/rich-text/src/component/use-input-and-selection.js b/packages/rich-text/src/component/use-input-and-selection.js index 7f011f6ecb73ab..2e0eebea699112 100644 --- a/packages/rich-text/src/component/use-input-and-selection.js +++ b/packages/rich-text/src/component/use-input-and-selection.js @@ -224,7 +224,12 @@ export function useInputAndSelection( props ) { } function onFocus() { - const { record, isSelected, onSelectionChange } = propsRef.current; + const { + record, + isSelected, + onSelectionChange, + applyRecord, + } = propsRef.current; if ( ! isSelected ) { // We know for certain that on focus, the old selection is invalid. @@ -240,6 +245,7 @@ export function useInputAndSelection( props ) { }; onSelectionChange( index, index ); } else { + applyRecord( record.current ); onSelectionChange( record.current.start, record.current.end ); } From c0c8ca055b0e8d018b2de5cab55320dc99d608a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 3 Sep 2021 09:22:54 +0200 Subject: [PATCH 141/214] Block Editor: Migrate `lightBlockWrapper` support to `apiVersion` for blocks (#34459) --- .../src/components/block-edit/edit.js | 5 +--- .../src/components/block-list/block.js | 6 +---- .../block-list/use-block-props/index.js | 9 +++----- .../use-block-custom-class-name.js | 6 ++--- .../use-block-default-class-name.js | 10 ++------ packages/block-editor/src/hooks/compat.js | 23 +++++++++++++++++++ packages/block-editor/src/hooks/index.js | 1 + .../block-editor/src/hooks/index.native.js | 1 + packages/blocks/src/api/serializer.js | 7 +----- 9 files changed, 35 insertions(+), 33 deletions(-) create mode 100644 packages/block-editor/src/hooks/compat.js diff --git a/packages/block-editor/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js index 0746699eb145dc..94b1adb4741095 100644 --- a/packages/block-editor/src/components/block-edit/edit.js +++ b/packages/block-editor/src/components/block-edit/edit.js @@ -51,10 +51,7 @@ export const Edit = ( props ) => { // them preferentially as the render value for the block. const Component = blockType.edit || blockType.save; - if ( - blockType.apiVersion > 1 || - hasBlockSupport( blockType, 'lightBlockWrapper', false ) - ) { + if ( blockType.apiVersion > 1 ) { return <Component { ...props } context={ context } />; } diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index c30921c4771347..960e3a6117297c 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -17,7 +17,6 @@ import { getBlockType, getSaveContent, isUnmodifiedDefaultBlock, - hasBlockSupport, } from '@wordpress/blocks'; import { withFilters } from '@wordpress/components'; import { withDispatch, withSelect, useDispatch } from '@wordpress/data'; @@ -110,9 +109,6 @@ function BlockListBlock( { ); const blockType = getBlockType( name ); - const lightBlockWrapper = - blockType?.apiVersion > 1 || - hasBlockSupport( blockType, 'lightBlockWrapper', false ); // Determine whether the block has props to apply to the wrapper. if ( blockType?.getEditWrapperProps ) { @@ -159,7 +155,7 @@ function BlockListBlock( { </Block> </> ); - } else if ( lightBlockWrapper ) { + } else if ( blockType?.apiVersion > 1 ) { block = blockEdit; } else { block = <Block { ...wrapperProps }>{ blockEdit }</Block>; diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js index ff6c46c7948f40..4801e52709c96b 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/index.js +++ b/packages/block-editor/src/components/block-list/use-block-props/index.js @@ -11,7 +11,6 @@ import { __, sprintf } from '@wordpress/i18n'; import { __unstableGetBlockProps as getBlockProps, getBlockType, - hasBlockSupport, } from '@wordpress/blocks'; import { useMergeRefs } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; @@ -68,11 +67,11 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { index, mode, name, + blockApiVersion, blockTitle, isPartOfSelection, adjustScrolling, enableAnimation, - lightBlockWrapper, } = useSelect( ( select ) => { const { @@ -99,6 +98,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { index: getBlockIndex( clientId, rootClientId ), mode: getBlockMode( clientId ), name: blockName, + blockApiVersion: blockType?.apiVersion || 1, blockTitle: blockType?.title, isPartOfSelection: isSelected || isPartOfMultiSelection, adjustScrolling: @@ -106,9 +106,6 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { enableAnimation: ! isTyping() && getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD, - lightBlockWrapper: - blockType?.apiVersion > 1 || - hasBlockSupport( blockType, 'lightBlockWrapper', false ), }; }, [ clientId ] @@ -139,7 +136,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { const blockEditContext = useBlockEditContext(); // Ensures it warns only inside the `edit` implementation for the block. - if ( ! lightBlockWrapper && clientId === blockEditContext.clientId ) { + if ( blockApiVersion < 2 && clientId === blockEditContext.clientId ) { warning( `Block type "${ name }" must support API version 2 or higher to work correctly with "useBlockProps" method.` ); diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-block-custom-class-name.js b/packages/block-editor/src/components/block-list/use-block-props/use-block-custom-class-name.js index 9b1ea0dfa0953f..2bc250be56cb16 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-block-custom-class-name.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-block-custom-class-name.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { useSelect } from '@wordpress/data'; -import { hasBlockSupport, getBlockType } from '@wordpress/blocks'; +import { getBlockType } from '@wordpress/blocks'; /** * Internal dependencies @@ -32,9 +32,7 @@ export function useBlockCustomClassName( clientId ) { } const blockType = getBlockType( getBlockName( clientId ) ); - const hasLightBlockWrapper = - blockType?.apiVersion > 1 || - hasBlockSupport( blockType, 'lightBlockWrapper', false ); + const hasLightBlockWrapper = blockType?.apiVersion > 1; if ( ! hasLightBlockWrapper ) { return; diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-block-default-class-name.js b/packages/block-editor/src/components/block-list/use-block-props/use-block-default-class-name.js index fa84fd8d0be1d7..7877ceb96490ca 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-block-default-class-name.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-block-default-class-name.js @@ -2,11 +2,7 @@ * WordPress dependencies */ import { useSelect } from '@wordpress/data'; -import { - hasBlockSupport, - getBlockType, - getBlockDefaultClassName, -} from '@wordpress/blocks'; +import { getBlockType, getBlockDefaultClassName } from '@wordpress/blocks'; /** * Internal dependencies @@ -26,9 +22,7 @@ export function useBlockDefaultClassName( clientId ) { ( select ) => { const name = select( blockEditorStore ).getBlockName( clientId ); const blockType = getBlockType( name ); - const hasLightBlockWrapper = - blockType?.apiVersion > 1 || - hasBlockSupport( blockType, 'lightBlockWrapper', false ); + const hasLightBlockWrapper = blockType?.apiVersion > 1; if ( ! hasLightBlockWrapper ) { return; diff --git a/packages/block-editor/src/hooks/compat.js b/packages/block-editor/src/hooks/compat.js new file mode 100644 index 00000000000000..515d26dae51645 --- /dev/null +++ b/packages/block-editor/src/hooks/compat.js @@ -0,0 +1,23 @@ +/** + * WordPress dependencies + */ +import { hasBlockSupport } from '@wordpress/blocks'; +import { addFilter } from '@wordpress/hooks'; + +function migrateLightBlockWrapper( settings ) { + const { apiVersion = 1 } = settings; + if ( + apiVersion < 2 && + hasBlockSupport( settings, 'lightBlockWrapper', false ) + ) { + settings.apiVersion = 2; + } + + return settings; +} + +addFilter( + 'blocks.registerBlockType', + 'core/compat/migrateLightBlockWrapper', + migrateLightBlockWrapper +); diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index e8a976277f9702..7572b4792812fd 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -1,6 +1,7 @@ /** * Internal dependencies */ +import './compat'; import './align'; import './anchor'; import './custom-class-name'; diff --git a/packages/block-editor/src/hooks/index.native.js b/packages/block-editor/src/hooks/index.native.js index b0aac50354da6b..82312c905ce551 100644 --- a/packages/block-editor/src/hooks/index.native.js +++ b/packages/block-editor/src/hooks/index.native.js @@ -1,6 +1,7 @@ /** * Internal dependencies */ +import './compat'; import './align'; import './anchor'; import './custom-class-name'; diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js index c88828b9cda799..a5e8e679b45832 100644 --- a/packages/blocks/src/api/serializer.js +++ b/packages/blocks/src/api/serializer.js @@ -18,7 +18,6 @@ import { getBlockType, getFreeformContentHandlerName, getUnregisteredTypeHandlerName, - hasBlockSupport, } from './registration'; import { isUnmodifiedDefaultBlock, normalizeBlockType } from './utils'; import BlockContentProvider from '../block-content-provider'; @@ -118,14 +117,10 @@ export function getSaveElement( let element = save( { attributes, innerBlocks } ); - const hasLightBlockWrapper = - blockType.apiVersion > 1 || - hasBlockSupport( blockType, 'lightBlockWrapper', false ); - if ( isObject( element ) && hasFilter( 'blocks.getSaveContent.extraProps' ) && - ! hasLightBlockWrapper + ! ( blockType.apiVersion > 1 ) ) { /** * Filters the props applied to the block save result element. From 1ad7a0ba48358ae2b6ec340f9472defa0d112910 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 3 Sep 2021 09:02:24 +0100 Subject: [PATCH 142/214] Add default editor styles applied to themes without theme.json and without editor styles (#34439) Co-authored-by: jasmussen <joen@automattic.com> Co-authored-by: Kjell Reigstad <kjell.reigstad@automattic.com> --- .../wordpress-5.9/default-editor-styles.php | 38 +++++++++++++++++++ lib/global-styles.php | 12 +++--- lib/load.php | 1 + .../src/components/editor-styles/index.js | 1 + .../src/default-editor-styles.scss | 24 ++++++++++++ packages/edit-post/src/editor.js | 4 +- 6 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 lib/compat/wordpress-5.9/default-editor-styles.php create mode 100644 packages/block-editor/src/default-editor-styles.scss diff --git a/lib/compat/wordpress-5.9/default-editor-styles.php b/lib/compat/wordpress-5.9/default-editor-styles.php new file mode 100644 index 00000000000000..9ad83c1b19f8e0 --- /dev/null +++ b/lib/compat/wordpress-5.9/default-editor-styles.php @@ -0,0 +1,38 @@ +<?php +/** + * Loads the default editor styles. + * + * @package gutenberg + */ + +/** + * Load the default editor styles. + * These styles are used if the "no theme styles" options is triggered + * or on themes without their own editor styles. + * + * @param array $settings Default editor settings. + * + * @return array Filtered editor settings. + */ +function gutenberg_extend_block_editor_settings_with_default_editor_styles( $settings ) { + $default_editor_styles_file = gutenberg_dir_path() . 'build/block-editor/default-editor-styles.css'; + $settings['defaultEditorStyles'] = array( + array( + 'css' => file_get_contents( $default_editor_styles_file ), + ), + ); + + // Remove the default font addition from Core Code. + $styles_without_core_styles = array(); + if ( isset( $settings['styles'] ) ) { + foreach ( $settings['styles'] as $style ) { + if ( 'core' !== $style['__unstableType'] ) { + $styles_without_core_styles[] = $style; + } + } + } + $settings['styles'] = $styles_without_core_styles; + + return $settings; +} +add_filter( 'block_editor_settings_all', 'gutenberg_extend_block_editor_settings_with_default_editor_styles' ); diff --git a/lib/global-styles.php b/lib/global-styles.php index 9469b79d06448d..59e392830517bc 100644 --- a/lib/global-styles.php +++ b/lib/global-styles.php @@ -139,15 +139,17 @@ function_exists( 'gutenberg_is_edit_site_page' ) && } // Reset existing global styles. - foreach ( $settings['styles'] as $key => $style ) { - if ( isset( $style['__unstableType'] ) && 'globalStyles' === $style['__unstableType'] ) { - unset( $settings['styles'][ $key ] ); + $styles_without_existing_global_styles = array(); + foreach ( $settings['styles'] as $style ) { + if ( ! isset( $style['__unstableType'] ) || 'globalStyles' !== $style['__unstableType'] ) { + $styles_without_existing_global_styles[] = $style; } } // Add the new ones. - $settings['styles'][] = $css_variables; - $settings['styles'][] = $block_styles; + $styles_without_existing_global_styles[] = $css_variables; + $styles_without_existing_global_styles[] = $block_styles; + $settings['styles'] = $styles_without_existing_global_styles; } // Copied from get_block_editor_settings() at wordpress-develop/block-editor.php. diff --git a/lib/load.php b/lib/load.php index 57692dc37319ca..096fe553d95e13 100644 --- a/lib/load.php +++ b/lib/load.php @@ -83,6 +83,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat.php'; require __DIR__ . '/compat/wordpress-5.8/index.php'; require __DIR__ . '/compat/wordpress-5.8.1/index.php'; +require __DIR__ . '/compat/wordpress-5.9/default-editor-styles.php'; require __DIR__ . '/utils.php'; require __DIR__ . '/editor-settings.php'; diff --git a/packages/block-editor/src/components/editor-styles/index.js b/packages/block-editor/src/components/editor-styles/index.js index 4b0286d8eba71a..c2c5f77ff600b6 100644 --- a/packages/block-editor/src/components/editor-styles/index.js +++ b/packages/block-editor/src/components/editor-styles/index.js @@ -68,6 +68,7 @@ export default function EditorStyles( { styles } ) { () => transformStyles( styles, EDITOR_STYLES_SELECTOR ), [ styles ] ); + return ( <> { /* Use an empty style element to have a document reference, diff --git a/packages/block-editor/src/default-editor-styles.scss b/packages/block-editor/src/default-editor-styles.scss new file mode 100644 index 00000000000000..e63291a30d7d1d --- /dev/null +++ b/packages/block-editor/src/default-editor-styles.scss @@ -0,0 +1,24 @@ +/** + * Default editor styles. + * + * These styles are shown if a theme does not register its own editor style, + * a theme.json file, or has toggled off "Use theme styles" in preferences. + */ + +body { + font-family: $default-font; + font-size: 18px; + line-height: 1.5; + --wp--style--block-gap: 2em; +} + +p { + line-height: 1.8; +} + +.editor-post-title__block { + margin-top: 2em; + margin-bottom: 1em; + font-size: 2.5em; + font-weight: 800; +} diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index e2781d1a6a4446..3f34c2a5f32bd2 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -156,7 +156,9 @@ function Editor( { ] ); const styles = useMemo( () => { - return hasThemeStyles ? settings.styles : []; + return hasThemeStyles && settings.styles?.length + ? settings.styles + : settings.defaultEditorStyles; }, [ settings, hasThemeStyles ] ); if ( ! post ) { From 0d6a201ff902267dc7b48e9be355f70c2717e5ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Fri, 3 Sep 2021 12:42:16 +0200 Subject: [PATCH 143/214] Remove colors classes from the packages that are already provided by global styles (#34510) --- packages/base-styles/CHANGELOG.md | 4 + packages/base-styles/_mixins.scss | 158 ++----------------------- packages/block-library/CHANGELOG.md | 4 + packages/block-library/src/common.scss | 12 +- packages/block-library/src/editor.scss | 11 +- 5 files changed, 24 insertions(+), 165 deletions(-) diff --git a/packages/base-styles/CHANGELOG.md b/packages/base-styles/CHANGELOG.md index 7faa28d3063d59..cdb47947993e64 100644 --- a/packages/base-styles/CHANGELOG.md +++ b/packages/base-styles/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Change + +- Remove the background-colors, foreground-colors, and gradient-colors mixins. + ## 2.0.0 (2020-07-07) ### Breaking Changes diff --git a/packages/base-styles/_mixins.scss b/packages/base-styles/_mixins.scss index fed727e4e4a6e1..f3e24b1f99d621 100644 --- a/packages/base-styles/_mixins.scss +++ b/packages/base-styles/_mixins.scss @@ -479,186 +479,48 @@ } } -@mixin background-colors() { - .has-pale-pink-background-color { - background-color: #f78da7; - } - - .has-vivid-red-background-color { - background-color: #cf2e2e; - } - - .has-luminous-vivid-orange-background-color { - background-color: #ff6900; - } - - .has-luminous-vivid-amber-background-color { - background-color: #fcb900; - } - - .has-light-green-cyan-background-color { - background-color: #7bdcb5; - } - - .has-vivid-green-cyan-background-color { - background-color: #00d084; - } - - .has-pale-cyan-blue-background-color { - background-color: #8ed1fc; - } - - .has-vivid-cyan-blue-background-color { - background-color: #0693e3; - } - - .has-vivid-purple-background-color { - background-color: #9b51e0; - } - - .has-white-background-color { - background-color: #fff; - } - - // Deprecated from UI, kept for back-compat. +// Deprecated from UI, kept for back-compat. +@mixin background-colors-deprecated() { .has-very-light-gray-background-color { background-color: #eee; } - .has-cyan-bluish-gray-background-color { - background-color: #abb8c3; - } - - // Deprecated from UI, kept for back-compat. .has-very-dark-gray-background-color { background-color: #313131; } - - .has-black-background-color { - background-color: #000; - } } -@mixin foreground-colors() { - .has-pale-pink-color { - color: #f78da7; - } - - .has-vivid-red-color { - color: #cf2e2e; - } - - .has-luminous-vivid-orange-color { - color: #ff6900; - } - - .has-luminous-vivid-amber-color { - color: #fcb900; - } - - .has-light-green-cyan-color { - color: #7bdcb5; - } - - .has-vivid-green-cyan-color { - color: #00d084; - } - - .has-pale-cyan-blue-color { - color: #8ed1fc; - } - - .has-vivid-cyan-blue-color { - color: #0693e3; - } - - .has-vivid-purple-color { - color: #9b51e0; - } - - .has-white-color { - color: #fff; - } - - // Deprecated from UI, kept for back-compat. +// Deprecated from UI, kept for back-compat. +@mixin foreground-colors-deprecated() { .has-very-light-gray-color { color: #eee; } - .has-cyan-bluish-gray-color { - color: #abb8c3; - } - - // Deprecated from UI, kept for back-compat. .has-very-dark-gray-color { color: #313131; } - - .has-black-color { - color: #000; - } } -@mixin gradient-colors() { - // Our classes uses the same values we set for gradient value attributes, and we can not use spacing because of WP multi site kses rule. +// Deprecated from UI, kept for back-compat. +@mixin gradient-colors-deprecated() { + /* + * Our classes uses the same values we set for gradient value attributes, + * and we can not use spacing because of WP multi site kses rule. + */ /* stylelint-disable function-comma-space-after */ - .has-vivid-cyan-blue-to-vivid-purple-gradient-background { - background: linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%); - } - .has-vivid-green-cyan-to-vivid-cyan-blue-gradient-background { background: linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%); } - .has-light-green-cyan-to-vivid-green-cyan-gradient-background { - background: linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%); - } - - .has-luminous-vivid-amber-to-luminous-vivid-orange-gradient-background { - background: linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%); - } - - .has-luminous-vivid-orange-to-vivid-red-gradient-background { - background: linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%); - } - - .has-very-light-gray-to-cyan-bluish-gray-gradient-background { - background: linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%); - } - - .has-cool-to-warm-spectrum-gradient-background { - background: linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%); - } - - .has-blush-light-purple-gradient-background { - background: linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%); - } - - .has-blush-bordeaux-gradient-background { - background: linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%); - } - .has-purple-crush-gradient-background { background: linear-gradient(135deg,rgb(52,226,228) 0%,rgb(71,33,251) 50%,rgb(171,29,254) 100%); } - .has-luminous-dusk-gradient-background { - background: linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%); - } - .has-hazy-dawn-gradient-background { background: linear-gradient(135deg,rgb(250,172,168) 0%,rgb(218,208,236) 100%); } - .has-pale-ocean-gradient-background { - background: linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%); - } - - .has-electric-grass-gradient-background { - background: linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%); - } - .has-subdued-olive-gradient-background { background: linear-gradient(135deg,rgb(250,250,225) 0%,rgb(103,166,113) 100%); } diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 709ed1182a2504..eccd2681c7d277 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Change + +- Remove the background-colors, foreground-colors, and gradient-colors mixins. + ## 5.0.0 (2021-07-29) ### Breaking Change diff --git a/packages/block-library/src/common.scss b/packages/block-library/src/common.scss index 6cf9854943376d..de0dacc67815ed 100644 --- a/packages/block-library/src/common.scss +++ b/packages/block-library/src/common.scss @@ -3,15 +3,9 @@ // the colors override the added specificity by link states such as :hover. :root { - // Background colors. - @include background-colors(); - - // Foreground colors. - @include foreground-colors(); - - // Gradients - @include gradient-colors(); - + @include background-colors-deprecated(); + @include foreground-colors-deprecated(); + @include gradient-colors-deprecated(); } // Font sizes. diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 0b67ea4296b94c..b1919397804a24 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -48,14 +48,9 @@ @import "./term-description/editor.scss"; :root .editor-styles-wrapper { - // Background colors. - @include background-colors(); - - // Foreground colors. - @include foreground-colors(); - - // Gradients - @include gradient-colors(); + @include background-colors-deprecated(); + @include foreground-colors-deprecated(); + @include gradient-colors-deprecated(); } // Font sizes. From c1b800ad71c9e723df2f7d7db750dc9ff254a218 Mon Sep 17 00:00:00 2001 From: Adam Zielinski <adam@adamziel.com> Date: Fri, 3 Sep 2021 13:12:57 +0200 Subject: [PATCH 144/214] =?UTF-8?q?Remove=20extraction=20of=20raw=20values?= =?UTF-8?q?=20in=20saveEntityRecords=20=E2=80=93=20wrapped=20values=20are?= =?UTF-8?q?=20accepted=20by=20the=20API=20and=20store=20without=20any=20is?= =?UTF-8?q?sues=20(#34502)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core-data/src/actions.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 104cf63348a081..0cc8bdaa7aefeb 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { castArray, get, isEqual, find } from 'lodash'; +import { castArray, isEqual, find } from 'lodash'; import { v4 as uuid } from 'uuid'; /** @@ -425,8 +425,7 @@ export const saveEntityRecord = ( if ( [ 'title', 'excerpt', 'content' ].includes( key ) ) { - // Edits should be the "raw" attribute values. - acc[ key ] = get( data[ key ], 'raw', data[ key ] ); + acc[ key ] = data[ key ]; } return acc; }, @@ -461,12 +460,7 @@ export const saveEntityRecord = ( key ) ) { - // Edits should be the "raw" attribute values. - acc[ key ] = get( - newRecord[ key ], - 'raw', - newRecord[ key ] - ); + acc[ key ] = newRecord[ key ]; } else if ( key === 'status' ) { // Status is only persisted in autosaves when going from // "auto-draft" to "draft". @@ -477,11 +471,7 @@ export const saveEntityRecord = ( : persistedRecord.status; } else { // These properties are not persisted in autosaves. - acc[ key ] = get( - persistedRecord[ key ], - 'raw', - persistedRecord[ key ] - ); + acc[ key ] = persistedRecord[ key ]; } return acc; }, From 4dafc47f31a86bfbd5d1b1c9b56e0b803e920925 Mon Sep 17 00:00:00 2001 From: Adam Zielinski <adam@adamziel.com> Date: Fri, 3 Sep 2021 14:14:06 +0200 Subject: [PATCH 145/214] Add isRawAttribute to entity config (#34388) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add rawBlockMarkupFields * Add unit tests for getRawEntityRecord * Rename rawBlockMarkupFields to rawAttributes * Use POST_RAW_ATTRIBUTES instead of an inline list * Rename rawBlockMarkupFields to isRawAttribute * Add isRawAttribute selector * Make isRawAttribute a utility function instead of a selector * Remove isRawAttribute from saveEntityRecord – the logic is deceivingly similar, but is about including specific attributes regardless of their "raw-ness" * Add rawAttributes to mobile postTypeEntities * Lint * Lint * Lint --- packages/core-data/src/entities.js | 4 ++ packages/core-data/src/selectors.js | 22 +++--- packages/core-data/src/test/selectors.js | 71 +++++++++++++++++++ packages/core-data/src/utils/index.js | 1 + .../core-data/src/utils/is-raw-attribute.js | 11 +++ .../src/utils/test/is-raw-attribute.js | 22 ++++++ .../src/components/provider/index.native.js | 1 + 7 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 packages/core-data/src/utils/is-raw-attribute.js create mode 100644 packages/core-data/src/utils/test/is-raw-attribute.js diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index f22010c58dddc6..c1783c85850b31 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -18,6 +18,8 @@ import { STORE_NAME } from './name'; export const DEFAULT_ENTITY_KEY = 'id'; +const POST_RAW_ATTRIBUTES = [ 'title', 'excerpt', 'content' ]; + export const defaultEntities = [ { label: __( 'Base' ), @@ -41,6 +43,7 @@ export const defaultEntities = [ key: 'slug', baseURL: '/wp/v2/types', baseURLParams: { context: 'edit' }, + rawAttributes: POST_RAW_ATTRIBUTES, }, { name: 'media', @@ -184,6 +187,7 @@ function* loadPostTypeEntities() { selection: true, }, mergedEdits: { meta: true }, + rawAttributes: POST_RAW_ATTRIBUTES, getTitle: ( record ) => record?.title?.rendered || record?.title || diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index a7bb82da615ab8..b3ab91d780960c 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -17,7 +17,7 @@ import deprecated from '@wordpress/deprecated'; import { STORE_NAME } from './name'; import { getQueriedItems } from './queried-data'; import { DEFAULT_ENTITY_KEY } from './entities'; -import { getNormalizedCommaSeparable } from './utils'; +import { getNormalizedCommaSeparable, isRawAttribute } from './utils'; /** * Shared reference to an empty array for cases where it is important to avoid @@ -205,14 +205,18 @@ export const getRawEntityRecord = createSelector( return ( record && Object.keys( record ).reduce( ( accumulator, _key ) => { - // Because edits are the "raw" attribute values, - // we return those from record selectors to make rendering, - // comparisons, and joins with edits easier. - accumulator[ _key ] = get( - record[ _key ], - 'raw', - record[ _key ] - ); + if ( isRawAttribute( getEntity( state, kind, name ), _key ) ) { + // Because edits are the "raw" attribute values, + // we return those from record selectors to make rendering, + // comparisons, and joins with edits easier. + accumulator[ _key ] = get( + record[ _key ], + 'raw', + record[ _key ] + ); + } else { + accumulator[ _key ] = record[ _key ]; + } return accumulator; }, {} ) ); diff --git a/packages/core-data/src/test/selectors.js b/packages/core-data/src/test/selectors.js index 586d39985e0c56..f76d60a09a4ab7 100644 --- a/packages/core-data/src/test/selectors.js +++ b/packages/core-data/src/test/selectors.js @@ -11,6 +11,7 @@ import { __experimentalGetEntityRecordNoResolver, hasEntityRecords, getEntityRecords, + getRawEntityRecord, __experimentalGetDirtyEntityRecords, __experimentalGetEntitiesBeingSaved, getEntityRecordNonTransientEdits, @@ -204,6 +205,76 @@ describe( 'hasEntityRecords', () => { } ); } ); +describe( 'getRawEntityRecord', () => { + const data = { + someKind: { + someName: { + queriedData: { + items: { + default: { + post: { + title: { + raw: { html: '<h1>post</h1>' }, + rendered: + '<div id="post"><h1>rendered post</h1></div>', + }, + }, + }, + }, + itemIsComplete: { + default: { + post: true, + }, + }, + queries: {}, + }, + }, + }, + }; + it( 'should preserve the structure of `raw` field by default', () => { + const state = deepFreeze( { + entities: { + config: [ + { + kind: 'someKind', + name: 'someName', + }, + ], + data: { ...data }, + }, + } ); + expect( + getRawEntityRecord( state, 'someKind', 'someName', 'post' ) + ).toEqual( { + title: { + raw: { html: '<h1>post</h1>' }, + rendered: '<div id="post"><h1>rendered post</h1></div>', + }, + } ); + } ); + it( 'should flatten the structure of `raw` field for entities configured with rawAttributes', () => { + const state = deepFreeze( { + entities: { + config: [ + { + kind: 'someKind', + name: 'someName', + rawAttributes: [ 'title' ], + }, + ], + data: { ...data }, + }, + } ); + expect( + getRawEntityRecord( state, 'someKind', 'someName', 'post' ) + ).toEqual( { + title: { + html: '<h1>post</h1>', + }, + } ); + } ); +} ); + describe( 'getEntityRecords', () => { it( 'should return null by default', () => { const state = deepFreeze( { diff --git a/packages/core-data/src/utils/index.js b/packages/core-data/src/utils/index.js index 05e8d73bf7630f..a4f4bf81373cd6 100644 --- a/packages/core-data/src/utils/index.js +++ b/packages/core-data/src/utils/index.js @@ -5,3 +5,4 @@ export { default as ifNotResolved } from './if-not-resolved'; export { default as onSubKey } from './on-sub-key'; export { default as replaceAction } from './replace-action'; export { default as withWeakMapCache } from './with-weak-map-cache'; +export { default as isRawAttribute } from './is-raw-attribute'; diff --git a/packages/core-data/src/utils/is-raw-attribute.js b/packages/core-data/src/utils/is-raw-attribute.js new file mode 100644 index 00000000000000..f8e8d4de359a43 --- /dev/null +++ b/packages/core-data/src/utils/is-raw-attribute.js @@ -0,0 +1,11 @@ +/** + * Checks whether the attribute is a "raw" attribute or not. + * + * @param {Object} entity Entity data. + * @param {string} attribute Attribute name. + * + * @return {boolean} Is the attribute raw + */ +export default function isRawAttribute( entity, attribute ) { + return ( entity.rawAttributes || [] ).includes( attribute ); +} diff --git a/packages/core-data/src/utils/test/is-raw-attribute.js b/packages/core-data/src/utils/test/is-raw-attribute.js new file mode 100644 index 00000000000000..545fd7c84286fc --- /dev/null +++ b/packages/core-data/src/utils/test/is-raw-attribute.js @@ -0,0 +1,22 @@ +/** + * Internal dependencies + */ +import { isRawAttribute } from '../'; + +describe( 'isRawAttribute', () => { + it( 'should correctly assess that the attribute is not raw', () => { + const entity = { + kind: 'someKind', + name: 'someName', + }; + expect( isRawAttribute( entity, 'title' ) ).toBe( false ); + } ); + it( 'should correctly assess that the attribute is raw', () => { + const entity = { + kind: 'someKind', + name: 'someName', + rawAttributes: [ 'title' ], + }; + expect( isRawAttribute( entity, 'title' ) ).toBe( true ); + } ); +} ); diff --git a/packages/editor/src/components/provider/index.native.js b/packages/editor/src/components/provider/index.native.js index 470a8fdf729ef7..6cdf848cfe69fb 100644 --- a/packages/editor/src/components/provider/index.native.js +++ b/packages/editor/src/components/provider/index.native.js @@ -54,6 +54,7 @@ const postTypeEntities = [ mergedEdits: { meta: true, }, + rawAttributes: [ 'title', 'excerpt', 'content' ], } ) ); import { EditorHelpTopics } from '@wordpress/editor'; From e8a42288e99b9c0e5f2606dbc43a704275d22eb3 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Fri, 3 Sep 2021 07:36:19 -0500 Subject: [PATCH 146/214] Fix various React warnings in development log (#34428) * Fix missing React key error in QueryControls Replace the returned array of children with a React Fragment, to avoid the need to set `key` for each child and avoid the following error. ERROR Warning: Each child in a list should have a unique "key" prop. See https://reactjs.org/link/warning-keys for more information. * Fix ColorPalette font weight warning Replace invalid `medium` value with valid `500` value. This addresses the following warning found in the Metro server logs. ERROR Warning: Failed prop type: Invalid prop `fontWeight` of value `medium` supplied to `Text`, expected one of ["normal","bold","100","200","300","400","500","600","700","800","900"]. Bad object: { "fontSize": 16, "color": "#2e4453", "letterSpacing": 1.25, "fontWeight": "medium" } --- .../src/color-palette/style.native.scss | 2 +- .../src/query-controls/index.native.js | 64 ++++++++++--------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/packages/components/src/color-palette/style.native.scss b/packages/components/src/color-palette/style.native.scss index 3343dda21303ae..f398def870d1b3 100644 --- a/packages/components/src/color-palette/style.native.scss +++ b/packages/components/src/color-palette/style.native.scss @@ -40,5 +40,5 @@ .customTextAndroid { letter-spacing: 1.25; - font-weight: medium; + font-weight: 500; } diff --git a/packages/components/src/query-controls/index.native.js b/packages/components/src/query-controls/index.native.js index f7a7c24c12af9a..6ba18f646146e6 100644 --- a/packages/components/src/query-controls/index.native.js +++ b/packages/components/src/query-controls/index.native.js @@ -61,37 +61,39 @@ const QueryControls = memo( [ order, orderBy, onOrderByChange, onOrderChange ] ); - return [ - onOrderChange && onOrderByChange && ( - <SelectControl - label={ __( 'Order by' ) } - value={ `${ orderBy }/${ order }` } - options={ options } - onChange={ onChange } - hideCancelButton={ true } - /> - ), - onCategoryChange && ( - <CategorySelect - categoriesList={ categoriesList } - label={ __( 'Category' ) } - noOptionLabel={ __( 'All' ) } - selectedCategoryId={ selectedCategoryId } - onChange={ onCategoryChange } - hideCancelButton={ true } - /> - ), - onNumberOfItemsChange && ( - <RangeControl - label={ __( 'Number of items' ) } - value={ numberOfItems } - onChange={ onNumberOfItemsChange } - min={ minItems } - max={ maxItems } - required - /> - ), - ]; + return ( + <> + { onOrderChange && onOrderByChange && ( + <SelectControl + label={ __( 'Order by' ) } + value={ `${ orderBy }/${ order }` } + options={ options } + onChange={ onChange } + hideCancelButton={ true } + /> + ) } + { onCategoryChange && ( + <CategorySelect + categoriesList={ categoriesList } + label={ __( 'Category' ) } + noOptionLabel={ __( 'All' ) } + selectedCategoryId={ selectedCategoryId } + onChange={ onCategoryChange } + hideCancelButton={ true } + /> + ) } + { onNumberOfItemsChange && ( + <RangeControl + label={ __( 'Number of items' ) } + value={ numberOfItems } + onChange={ onNumberOfItemsChange } + min={ minItems } + max={ maxItems } + required + /> + ) } + </> + ); } ); From bdc7eee9f13955492937c70877a5b60c4e38b75c Mon Sep 17 00:00:00 2001 From: Antonis Lilis <antonis.lilis@automattic.com> Date: Fri, 3 Sep 2021 16:20:53 +0300 Subject: [PATCH 147/214] Mobile Release v1.61.0 (#34523) * Release script: Update react-native-editor version to 1.61.0 * Release script: Update with changes from 'npm run core preios' * Update changelog Co-authored-by: Matthew Kevins <mmkevins@yahoo.com> --- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/CHANGELOG.md | 4 ++++ packages/react-native-editor/ios/Gemfile.lock | 3 --- packages/react-native-editor/ios/Podfile.lock | 10 +++++----- packages/react-native-editor/package.json | 2 +- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index f188e0722eac0d..1945562695c360 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.60.1", + "version": "1.61.0", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index e0ffa44432a45f..cff35cf03a22e7 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.60.1", + "version": "1.61.0", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 0d8aec88a78d59..80ff9080ab94af 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -10,7 +10,11 @@ For each user feature we should also add a importance categorization label to i --> ## Unreleased + +## 1.61.0 - [**] Enable embed preview for a list of providers (for now only YouTube and Twitter) [#34446] +- [***] Inserter: Add Inserter Block Search [https://github.com/WordPress/gutenberg/pull/33237] + ## 1.60.1 - [*] RNmobile: Fix the cancel button on Block Variation Picker / Columns Block. [#34249] - [*] Column block: Fix Android close button alignment. [#34332] diff --git a/packages/react-native-editor/ios/Gemfile.lock b/packages/react-native-editor/ios/Gemfile.lock index 4bc064a3c23134..985f9107a3008d 100644 --- a/packages/react-native-editor/ios/Gemfile.lock +++ b/packages/react-native-editor/ios/Gemfile.lock @@ -1,6 +1,3 @@ -GEM - specs: - GEM remote: https://rubygems.org/ specs: diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index 6a01df08345d4a..4b3b86a26b03f6 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -12,7 +12,7 @@ PODS: - React-jsi (= 0.64.0) - ReactCommon/turbomodule/core (= 0.64.0) - glog (0.3.5) - - Gutenberg (1.60.1): + - Gutenberg (1.61.0): - React-Core (= 0.64.0) - React-CoreModules (= 0.64.0) - React-RCTImage (= 0.64.0) @@ -303,7 +303,7 @@ PODS: - React-Core - RNSVG (9.13.7-wp): - React-Core - - RNTAztecView (1.60.1): + - RNTAztecView (1.61.0): - React-Core - WordPress-Aztec-iOS (~> 1.19.4) - WordPress-Aztec-iOS (1.19.4) @@ -457,9 +457,9 @@ SPEC CHECKSUMS: BVLinearGradient: 2c791e973a3df0df46028210c530fde52c06b717 DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de FBLazyVector: 49cbe4b43e445b06bf29199b6ad2057649e4c8f5 - FBReactNativeSpec: 80e9cf1155002ee4720084d8813326d913815e2f + FBReactNativeSpec: ca068ae274cbd52c8638d4b44a8b9c6a35ae975e glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62 - Gutenberg: bc75d848519ff99520b0109a3d7f9c6f82caeae7 + Gutenberg: c0b1c47cf3c63f795570e83a12a5df2cb38d9c5e RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c RCTRequired: 2f8cb5b7533219bf4218a045f92768129cf7050a RCTTypeSafety: 512728b73549e72ad7330b92f3d42936f2a4de5b @@ -496,7 +496,7 @@ SPEC CHECKSUMS: RNReanimated: ca6105fdc2739ea1b3a7a5350b6490d8160143bc RNScreens: eb4e23256e7f2a5a1af87ea24dfeb49aea0ef310 RNSVG: 1b6dcbec5884b6dbe256bf8c38eeeab0acf05926 - RNTAztecView: 9611e25a2e52d206e7979081294b7c40c20d1bd5 + RNTAztecView: e632368bd658eb1ead726ce7ac1c010c6bd10a72 WordPress-Aztec-iOS: 870c93297849072aadfc2223e284094e73023e82 Yoga: 8c8436d4171c87504c648ae23b1d81242bdf3bbf diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index b98ae5b1ac0b4b..fc64075a376c56 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-editor", - "version": "1.60.1", + "version": "1.61.0", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 1921299596f871d4db7f9446a09f61da573006c0 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Fri, 3 Sep 2021 16:12:44 +0200 Subject: [PATCH 148/214] Fix text-menu min widths. (#34532) * Fix text-menu min widths. * Update changelog to indicate the min-width removal can be breaking. --- .../block-editor/src/components/media-placeholder/style.scss | 2 ++ packages/components/CHANGELOG.md | 4 ++++ packages/components/src/menu-item/style.scss | 3 +++ 3 files changed, 9 insertions(+) diff --git a/packages/block-editor/src/components/media-placeholder/style.scss b/packages/block-editor/src/components/media-placeholder/style.scss index 88fa0f90deddc9..e8eaa4bf43d94b 100644 --- a/packages/block-editor/src/components/media-placeholder/style.scss +++ b/packages/block-editor/src/components/media-placeholder/style.scss @@ -11,6 +11,8 @@ // Selector requires a lot of specificity to override base styles. input[type="url"].block-editor-media-placeholder__url-input-field { width: 100%; + min-width: 200px; + @include break-small() { width: 300px; } diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 03643e9e484c95..9b6ae2d6cc5dd7 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Change + +- Removed a min-width from the `DropdownMenu` component, allowing the menu to accommodate thin contents like vertical tools menus ([#33995](https://github.com/WordPress/gutenberg/pull/33995)). + ### Bug Fix - Fixed RTL styles in `Flex` component ([#33729](https://github.com/WordPress/gutenberg/pull/33729)). diff --git a/packages/components/src/menu-item/style.scss b/packages/components/src/menu-item/style.scss index 80f97da381947e..69e5c6f50fd7e4 100644 --- a/packages/components/src/menu-item/style.scss +++ b/packages/components/src/menu-item/style.scss @@ -52,7 +52,10 @@ } .components-menu-item__item { + // Provide a minimum width for text items in menus. white-space: nowrap; + min-width: 160px; + margin-right: auto; display: inline-flex; align-items: center; From 91de861685c716fc2c39c419d29f5ee747230bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <4710635+ellatrix@users.noreply.github.com> Date: Fri, 3 Sep 2021 17:45:34 +0300 Subject: [PATCH 149/214] Post title: remove class from old div to fix alignment (#34489) --- .../e2e-tests/specs/editor/various/block-deletion.test.js | 4 ++-- packages/e2e-tests/specs/editor/various/block-mover.test.js | 2 +- .../e2e-tests/specs/editor/various/splitting-merging.test.js | 4 ++-- packages/edit-post/src/components/text-editor/style.scss | 2 +- packages/editor/src/components/post-title/index.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/e2e-tests/specs/editor/various/block-deletion.test.js b/packages/e2e-tests/specs/editor/various/block-deletion.test.js index 00e239eae69593..8f79ae9eb9dc04 100644 --- a/packages/e2e-tests/specs/editor/various/block-deletion.test.js +++ b/packages/e2e-tests/specs/editor/various/block-deletion.test.js @@ -156,10 +156,10 @@ describe( 'deleting all blocks', () => { await page.keyboard.type( 'Paragraph' ); await clickOnBlockSettingsMenuRemoveBlockButton(); - // There is a default block: + // There is a default block and post title: expect( await page.$$( '.block-editor-block-list__block' ) - ).toHaveLength( 1 ); + ).toHaveLength( 2 ); // But the effective saved content is still empty: expect( await getEditedPostContent() ).toBe( '' ); diff --git a/packages/e2e-tests/specs/editor/various/block-mover.test.js b/packages/e2e-tests/specs/editor/various/block-mover.test.js index c3a04f6f3a6c4e..e0a17fcdceeca1 100644 --- a/packages/e2e-tests/specs/editor/various/block-mover.test.js +++ b/packages/e2e-tests/specs/editor/various/block-mover.test.js @@ -16,7 +16,7 @@ describe( 'block mover', () => { await page.keyboard.type( 'Second Paragraph' ); // Select a block so the block mover is rendered. - await page.focus( '.block-editor-block-list__block' ); + await page.focus( '[data-type="core/paragraph"]' ); await showBlockToolbar(); diff --git a/packages/e2e-tests/specs/editor/various/splitting-merging.test.js b/packages/e2e-tests/specs/editor/various/splitting-merging.test.js index 3317957ff5f1b7..fe7a069fe1c8a6 100644 --- a/packages/e2e-tests/specs/editor/various/splitting-merging.test.js +++ b/packages/e2e-tests/specs/editor/various/splitting-merging.test.js @@ -193,10 +193,10 @@ describe( 'splitting and merging blocks', () => { await insertBlock( 'Paragraph' ); await page.keyboard.press( 'Backspace' ); - // There is a default block: + // There is a default block and post title: expect( await page.$$( '.block-editor-block-list__block' ) - ).toHaveLength( 1 ); + ).toHaveLength( 2 ); // But the effective saved content is still empty: expect( await getEditedPostContent() ).toBe( '' ); diff --git a/packages/edit-post/src/components/text-editor/style.scss b/packages/edit-post/src/components/text-editor/style.scss index 57d63b894007e8..925e88df27180b 100644 --- a/packages/edit-post/src/components/text-editor/style.scss +++ b/packages/edit-post/src/components/text-editor/style.scss @@ -5,7 +5,7 @@ flex-grow: 1; // Post title. - .editor-post-title.editor-post-title__block { + .editor-post-title { max-width: none; line-height: $default-line-height; diff --git a/packages/editor/src/components/post-title/index.js b/packages/editor/src/components/post-title/index.js index ea99b0c815d2bd..7cea94d0625a14 100644 --- a/packages/editor/src/components/post-title/index.js +++ b/packages/editor/src/components/post-title/index.js @@ -168,7 +168,7 @@ export default function PostTitle() { // The wp-block className is important for editor styles. // This same block is used in both the visual and the code editor. const className = classnames( - 'wp-block editor-post-title editor-post-title__block editor-post-title__input rich-text', + 'wp-block wp-block-post-title block-editor-block-list__block editor-post-title editor-post-title__input rich-text', { 'is-selected': isSelected, 'is-focus-mode': isFocusMode, From 97c9f4f6a73ef66d9e84122f55d70966c8719208 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation <gutenberg@wordpress.org> Date: Fri, 3 Sep 2021 14:50:06 +0000 Subject: [PATCH 150/214] Bump plugin version to 11.4.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- readme.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 8b975db8bcd245..bc6e9ec3fdda9e 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the new block editor in core. * Requires at least: 5.6 * Requires PHP: 5.6 - * Version: 11.4.0 + * Version: 11.4.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index def5e7fc6713dc..9753141aae81ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "11.4.0", + "version": "11.4.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6b1d957ec8b053..a09653db09c072 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "11.4.0", + "version": "11.4.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", diff --git a/readme.txt b/readme.txt index 5a8efab129524a..f9ffcf44f33581 100644 --- a/readme.txt +++ b/readme.txt @@ -55,4 +55,4 @@ View <a href="https://developer.wordpress.org/block-editor/principles/versions-i == Changelog == -To read the changelog for Gutenberg 11.4.0, please navigate to the <a href="https://github.com/WordPress/gutenberg/releases/tag/v11.4.0">release page</a>. +To read the changelog for Gutenberg 11.4.1, please navigate to the <a href="https://github.com/WordPress/gutenberg/releases/tag/v11.4.1">release page</a>. From 4e664da5da01d7847240319b324323d125f154e3 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation <gutenberg@wordpress.org> Date: Fri, 3 Sep 2021 15:24:03 +0000 Subject: [PATCH 151/214] Update Changelog for 11.4.1 --- changelog.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/changelog.txt b/changelog.txt index 80a58d1902190a..d8694890cee2bc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,12 @@ == Changelog == += 11.4.1 = + +### Bug Fixes + +- Post title: Remove class from old div to fix alignment. ([34489](https://github.com/WordPress/gutenberg/pull/34489)) + + = 11.4.0 = ### Enhancements From 8ac696b2f0a2c2373aed2442e94efaac4805e6a9 Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Fri, 3 Sep 2021 17:50:09 +0200 Subject: [PATCH 152/214] [RNMobile][Embed block] Implement WP embed preview component (#34004) * Add native version of WP embed component * Add custom JS prop to sandbox * Use SandBox for rendering WP embed preview * Remove width calculation from sandbox We really only need to calculate the height as the width should fit the block's size. # Conflicts: # packages/components/src/sandbox/index.native.js * Update observe script of WP embed preview * Enable inline preview for WP embeds --- .../block-library/src/embed/edit.native.js | 12 +-- .../src/embed/embed-preview.native.js | 59 +++++++------- .../src/embed/wp-embed-preview.native.js | 80 +++++++++++++++++++ .../components/src/sandbox/index.native.js | 5 +- 4 files changed, 118 insertions(+), 38 deletions(-) create mode 100644 packages/block-library/src/embed/wp-embed-preview.native.js diff --git a/packages/block-library/src/embed/edit.native.js b/packages/block-library/src/embed/edit.native.js index deb4fe67e76032..ac9e1f372be027 100644 --- a/packages/block-library/src/embed/edit.native.js +++ b/packages/block-library/src/embed/edit.native.js @@ -211,6 +211,11 @@ const EmbedEdit = ( props ) => { } = getMergedAttributes(); const className = classnames( classFromPreview, props.className ); + const isProviderPreviewable = + PREVIEWABLE_PROVIDERS.includes( providerNameSlug ) || + // For WordPress embeds, we enable the inline preview for all its providers. + 'wp-embed' === type; + return ( <> { showEmbedPlaceholder ? ( @@ -247,12 +252,7 @@ const EmbedEdit = ( props ) => { label={ title } onFocus={ onFocus } preview={ preview } - previewable={ - previewable && - PREVIEWABLE_PROVIDERS.includes( - providerNameSlug - ) - } + previewable={ previewable && isProviderPreviewable } type={ type } url={ url } /> diff --git a/packages/block-library/src/embed/embed-preview.native.js b/packages/block-library/src/embed/embed-preview.native.js index 8670b3c2d22668..0beafe458ccca8 100644 --- a/packages/block-library/src/embed/embed-preview.native.js +++ b/packages/block-library/src/embed/embed-preview.native.js @@ -20,6 +20,7 @@ import { SandBox } from '@wordpress/components'; */ import { getPhotoHtml } from './util'; import EmbedNoPreview from './embed-no-preview'; +import WpEmbedPreview from './wp-embed-preview'; import styles from './styles.scss'; const EmbedPreview = ( { @@ -85,37 +86,35 @@ const EmbedPreview = ( { 'wp-block-embed__wrapper' ); - const embedWrapper = - /* We should render here: <WpEmbedPreview html={ html } /> */ - 'wp-embed' === type ? null : ( - <> - <TouchableWithoutFeedback - onPress={ () => { - if ( onFocus ) { - onFocus(); - } - if ( isCaptionSelected ) { - setIsCaptionSelected( false ); - } - } } + const PreviewContent = 'wp-embed' === type ? WpEmbedPreview : SandBox; + const embedWrapper = ( + <> + <TouchableWithoutFeedback + onPress={ () => { + if ( onFocus ) { + onFocus(); + } + if ( isCaptionSelected ) { + setIsCaptionSelected( false ); + } + } } + > + <View + pointerEvents="box-only" + style={ [ wrapperStyle, wrapperAlignStyle ] } > - <View - pointerEvents="box-only" - style={ [ wrapperStyle, wrapperAlignStyle ] } - > - <SandBox - html={ html } - title={ iframeTitle } - type={ sandboxClassnames } - providerUrl={ providerUrl } - url={ url } - containerStyle={ sandboxAlignStyle } - /> - </View> - </TouchableWithoutFeedback> - </> - ); - + <PreviewContent + html={ html } + title={ iframeTitle } + type={ sandboxClassnames } + providerUrl={ providerUrl } + url={ url } + containerStyle={ sandboxAlignStyle } + /> + </View> + </TouchableWithoutFeedback> + </> + ); return ( <TouchableWithoutFeedback accessible={ ! isSelected } diff --git a/packages/block-library/src/embed/wp-embed-preview.native.js b/packages/block-library/src/embed/wp-embed-preview.native.js new file mode 100644 index 00000000000000..a4b44f322df404 --- /dev/null +++ b/packages/block-library/src/embed/wp-embed-preview.native.js @@ -0,0 +1,80 @@ +/** + * WordPress dependencies + */ +import { memo, useMemo } from '@wordpress/element'; +import { SandBox } from '@wordpress/components'; + +/** + * Checks for WordPress embed events signaling the height change when iframe + * content loads or iframe's window is resized. The event is sent from + * WordPress core via the window.postMessage API. + * + * References: + * window.postMessage: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage + * WordPress core embed-template on load: https://github.com/WordPress/WordPress/blob/HEAD/wp-includes/js/wp-embed-template.js#L143 + * WordPress core embed-template on resize: https://github.com/WordPress/WordPress/blob/HEAD/wp-includes/js/wp-embed-template.js#L187 + */ +const observeAndResizeJS = ` + ( function() { + if ( ! document.body || ! window.parent ) { + return; + } + + function sendResize( { data: { secret, message, value } = {} } ) { + if ( + [ secret, message, value ].some( + ( attribute ) => ! attribute + ) || + message !== 'height' + ) { + return; + } + + document + .querySelectorAll( 'iframe[data-secret="' + secret + '"' ) + .forEach( ( iframe ) => { + if ( +iframe.height !== value ) { + iframe.height = value; + } + } ); + + // The function postMessage is exposed by the react-native-webview library + // to communicate between React Native and the WebView, in this case, + // we use it for notifying resize changes. + window.ReactNativeWebView.postMessage(JSON.stringify( { + action: 'resize', + height: value, + })); + } + + window.addEventListener( 'message', sendResize ); +} )();`; + +function WpEmbedPreview( { html, ...rest } ) { + const wpEmbedHtml = useMemo( () => { + const doc = new window.DOMParser().parseFromString( html, 'text/html' ); + const iframe = doc.querySelector( 'iframe' ); + + if ( iframe ) { + iframe.removeAttribute( 'style' ); + } + + const blockQuote = doc.querySelector( 'blockquote' ); + + if ( blockQuote ) { + blockQuote.innerHTML = ''; + } + + return doc.body.innerHTML; + }, [ html ] ); + + return ( + <SandBox + customJS={ observeAndResizeJS } + html={ wpEmbedHtml } + { ...rest } + /> + ); +} + +export default memo( WpEmbedPreview ); diff --git a/packages/components/src/sandbox/index.native.js b/packages/components/src/sandbox/index.native.js index ef08c6f2729d14..cde05eeca8fcb6 100644 --- a/packages/components/src/sandbox/index.native.js +++ b/packages/components/src/sandbox/index.native.js @@ -154,6 +154,7 @@ const EMPTY_ARRAY = []; function Sandbox( { containerStyle, html = '', + customJS, providerUrl = '', scripts = EMPTY_ARRAY, styles = EMPTY_ARRAY, @@ -209,7 +210,7 @@ function Sandbox( { <script type="text/javascript" dangerouslySetInnerHTML={ { - __html: observeAndResizeJS, + __html: customJS || observeAndResizeJS, } } /> { scripts.map( ( src ) => ( @@ -257,7 +258,7 @@ function Sandbox( { function getSizeStyle() { const contentHeight = Math.ceil( height ); - return { height: contentHeight }; + return contentHeight ? { height: contentHeight } : { aspectRatio: 1 }; } function onChangeDimensions( dimensions ) { From ca67d603637950bc08291ed9208a3f2d2e3e67c2 Mon Sep 17 00:00:00 2001 From: Siobhan Bamber <SiobhyB@users.noreply.github.com> Date: Fri, 3 Sep 2021 17:15:04 +0100 Subject: [PATCH 153/214] [RNMobile] Resolve Text Entry Issues/Errors in Alt Text Settings (#33975) * Resolve render error This commit aims to fix the render error outlined here: https://github.com/WordPress/gutenberg/pull/33057#pullrequestreview-725305368 The "outside" changes that happen when text is changed in the BottomSheetTextControl component should not be called directly from that component. Instead, they're now called within the useEffect hook. * Replace "value" prop with "defaultValue" The purpose of this commit is to address the text entry issues that currently exist for the TextInput component, as described here: https://github.com/facebook/react-native/issues/30503 A working workaround propose in that thread is to replace the "value" prop with "defaultValue". * Simplify component by removing unecessary hooks The introduction of the "defaultValue" prop in https://github.com/WordPress/gutenberg/pull/33975/commits/f6d9697ab66a05e43d253f853de8ec74f12a73b6 means that it's no longer necessary to keep the value in state. "defaultValue" allows for text to be automatically changed as it's typed. See: https://reactnative.dev/docs/textinput#defaultvalue We don't need to use "onChangeText" for the purpose of updating the input's text and can instead use it to invoke the "onChange" function. We can also remove the useState and useEffect hooks, as they're not necessary to update the component's value. --- .../src/mobile/bottom-sheet-text-control/index.native.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/components/src/mobile/bottom-sheet-text-control/index.native.js b/packages/components/src/mobile/bottom-sheet-text-control/index.native.js index c3bcb337d9ad52..1244e2b686aa04 100644 --- a/packages/components/src/mobile/bottom-sheet-text-control/index.native.js +++ b/packages/components/src/mobile/bottom-sheet-text-control/index.native.js @@ -42,8 +42,6 @@ const BottomSheetTextControl = ( { setShowSubSheet( true ); }; - const [ value, onChangeText ] = useState( initialValue ); - const horizontalBorderStyle = usePreferredColorSchemeStyle( styles.horizontalBorder, styles.horizontalBorderDark @@ -77,9 +75,8 @@ const BottomSheetTextControl = ( { <PanelBody style={ horizontalBorderStyle }> <TextInput label={ label } - onChangeText={ ( text ) => onChangeText( text ) } - onChange={ onChange( value ) } - value={ value } + onChangeText={ ( text ) => onChange( text ) } + defaultValue={ initialValue } multiline={ true } placeholder={ placeholder } placeholderTextColor={ '#87a6bc' } From 389a1cc273d629a089f4d28fb40eb39f6be371c5 Mon Sep 17 00:00:00 2001 From: Enej Bajgoric <enej.bajgoric@automattic.com> Date: Fri, 3 Sep 2021 14:56:42 -0700 Subject: [PATCH 154/214] [Mobile] - add CSS Unit parser to px (#34186) * Add getPxFromCssUnit * Add support for css functions * Code clean up + adding support for lh and Q units * Fix numeric value return px value in calculation or not * Update packages/block-editor/src/utils/test/parse-css-unit-to-px.js Co-authored-by: Gerardo Pacheco <gerardo.pacheco@automattic.com> Co-authored-by: Gerardo Pacheco <gerardo.pacheco@automattic.com> --- .../src/utils/parse-css-unit-to-px.js | 230 ++++++++++++++++++ .../src/utils/test/parse-css-unit-to-px.js | 183 ++++++++++++++ 2 files changed, 413 insertions(+) create mode 100644 packages/block-editor/src/utils/parse-css-unit-to-px.js create mode 100644 packages/block-editor/src/utils/test/parse-css-unit-to-px.js diff --git a/packages/block-editor/src/utils/parse-css-unit-to-px.js b/packages/block-editor/src/utils/parse-css-unit-to-px.js new file mode 100644 index 00000000000000..c8451fcc3e1d65 --- /dev/null +++ b/packages/block-editor/src/utils/parse-css-unit-to-px.js @@ -0,0 +1,230 @@ +/** + * Converts string to object { value, unit }. + * + * @param {string} cssUnit + * @return {Object} parsedUnit + */ +function parseUnit( cssUnit ) { + const match = cssUnit + ?.trim() + .match( + /^(0?[-.]?\d+)(r?e[m|x]|v[h|w|min|max]+|p[x|t|c]|[c|m]m|%|in|ch|Q|lh)$/ + ); + if ( ! isNaN( cssUnit ) && ! isNaN( parseFloat( cssUnit ) ) ) { + return { value: parseFloat( cssUnit ), unit: 'px' }; + } + return match + ? { value: parseFloat( match[ 1 ] ) || match[ 1 ], unit: match[ 2 ] } + : { value: cssUnit, unit: undefined }; +} +/** + * Evaluate a math expression. + * + * @param {string} expression + * @return {number} evaluated expression. + */ +function calculate( expression ) { + return Function( `'use strict'; return (${ expression })` )(); +} + +/** + * Calculates the css function value for the supported css functions such as max, min, clamp and calc. + * + * @param {string} functionUnitValue string should be in a particular format (for example min(12px,12px) ) no nested loops. + * @param {Object} options + * @return {string} unit containing the unit in PX. + */ +function getFunctionUnitValue( functionUnitValue, options ) { + const functionUnit = functionUnitValue.split( /[(),]/g ).filter( Boolean ); + + const units = functionUnit + .slice( 1 ) + .map( ( unit ) => parseUnit( getPxFromCssUnit( unit, options ) ).value ) + .filter( Boolean ); + + switch ( functionUnit[ 0 ] ) { + case 'min': + return Math.min( ...units ) + 'px'; + case 'max': + return Math.max( ...units ) + 'px'; + case 'clamp': + if ( units.length !== 3 ) { + return null; + } + if ( units[ 1 ] < units[ 0 ] ) { + return units[ 0 ] + 'px'; + } + if ( units[ 1 ] > units[ 2 ] ) { + return units[ 2 ] + 'px'; + } + return units[ 1 ] + 'px'; + case 'calc': + return units[ 0 ] + 'px'; + } +} + +/** + * Take a css function such as min, max, calc, clamp and returns parsedUnit + * + * How this works for the nested function is that it first replaces the inner function call. + * Then it tackles the outer onces. + * So for example: min( max(25px, 35px), 40px ) + * in the first pass we would replace max(25px, 35px) with 35px. + * then we would try to evaluate min( 35px, 40px ) + * and then finally return 35px. + * + * @param {string} cssUnit + * @return {Object} parsedUnit object. + */ +function parseUnitFunction( cssUnit ) { + while ( true ) { + const currentCssUnit = cssUnit; + const regExp = /(max|min|calc|clamp)\(([^()]*)\)/g; + const matches = regExp.exec( cssUnit ) || []; + if ( matches[ 0 ] ) { + const functionUnitValue = getFunctionUnitValue( matches[ 0 ] ); + cssUnit = cssUnit.replace( matches[ 0 ], functionUnitValue ); + } + + // if the unit hasn't been modified or we have a single value break free. + if ( cssUnit === currentCssUnit || parseFloat( cssUnit ) ) { + break; + } + } + + return parseUnit( cssUnit ); +} +/** + * Return true if we think this is a math expression. + * + * @param {string} cssUnit the cssUnit value being evaluted. + * @return {boolean} Whether the cssUnit is a math expression. + */ +function isMathExpression( cssUnit ) { + for ( let i = 0; i < cssUnit.length; i++ ) { + if ( [ '+', '-', '/', '*' ].includes( cssUnit[ i ] ) ) { + return true; + } + } + return false; +} +/** + * Evaluates the math expression and return a px value. + * + * @param {string} cssUnit the cssUnit value being evaluted. + * @return {string} return a converfted value to px. + */ +function evalMathExpression( cssUnit ) { + let errorFound = false; + // Convert every part of the expression to px values. + const cssUnitsBits = cssUnit.split( /[+-/*/]/g ).filter( Boolean ); + for ( const unit of cssUnitsBits ) { + // Standardize the unit to px and extract the value. + const parsedUnit = parseUnit( getPxFromCssUnit( unit ) ); + if ( ! parseFloat( parsedUnit.value ) ) { + errorFound = true; + // end early since we are dealing with a null value. + break; + } + cssUnit = cssUnit.replace( unit, parsedUnit.value ); + } + + return errorFound ? null : calculate( cssUnit ).toFixed( 0 ) + 'px'; +} +/** + * Convert a parsedUnit object to px value. + * + * @param {Object} parsedUnit + * @param {Object} options + * @return {string} or {null} returns the converted with in a px value format. + */ +function convertParsedUnitToPx( parsedUnit, options ) { + const PIXELS_PER_INCH = 96; + const ONE_PERCENT = 0.01; + + const defaultProperties = { + fontSize: 16, + lineHeight: 16, + width: 375, + height: 812, + type: 'font', + }; + + const setOptions = Object.assign( {}, defaultProperties, options ); + + const relativeUnits = { + em: setOptions.fontSize, + rem: setOptions.fontSize, + vh: setOptions.height * ONE_PERCENT, + vw: setOptions.width * ONE_PERCENT, + vmin: + ( setOptions.width < setOptions.height + ? setOptions.width + : setOptions.height ) * ONE_PERCENT, + vmax: + ( setOptions.width > setOptions.height + ? setOptions.width + : setOptions.height ) * ONE_PERCENT, + '%': + ( setOptions.type === 'font' + ? setOptions.fontSize + : setOptions.width ) * ONE_PERCENT, + ch: 8, // The advance measure (width) of the glyph "0" of the element's font. Approximate + ex: 7.15625, // x-height of the element's font. Approximate + lh: setOptions.lineHeight, + }; + + const absoluteUnits = { + in: PIXELS_PER_INCH, + cm: PIXELS_PER_INCH / 2.54, + mm: PIXELS_PER_INCH / 25.4, + pt: PIXELS_PER_INCH / 72, + pc: PIXELS_PER_INCH / 6, + px: 1, + Q: PIXELS_PER_INCH / 2.54 / 40, + }; + + if ( relativeUnits[ parsedUnit.unit ] ) { + return ( + ( relativeUnits[ parsedUnit.unit ] * parsedUnit.value ).toFixed( + 0 + ) + 'px' + ); + } + + if ( absoluteUnits[ parsedUnit.unit ] ) { + return ( + ( absoluteUnits[ parsedUnit.unit ] * parsedUnit.value ).toFixed( + 0 + ) + 'px' + ); + } + + return null; +} +/** + * Returns the px value of a cssUnit. + * + * @param {string} cssUnit + * @param {string} options + * @return {string} returns the cssUnit value in a simple px format. + */ +export function getPxFromCssUnit( cssUnit, options = {} ) { + if ( Number.isFinite( cssUnit ) ) { + return cssUnit.toFixed( 0 ) + 'px'; + } + if ( cssUnit === undefined ) { + return null; + } + let parsedUnit = parseUnit( cssUnit ); + + if ( ! parsedUnit.unit ) { + parsedUnit = parseUnitFunction( cssUnit, options ); + } + + if ( isMathExpression( cssUnit ) && ! parsedUnit.unit ) { + return evalMathExpression( cssUnit ); + } + + return convertParsedUnitToPx( parsedUnit, options ); +} diff --git a/packages/block-editor/src/utils/test/parse-css-unit-to-px.js b/packages/block-editor/src/utils/test/parse-css-unit-to-px.js new file mode 100644 index 00000000000000..90cfac3b55e27b --- /dev/null +++ b/packages/block-editor/src/utils/test/parse-css-unit-to-px.js @@ -0,0 +1,183 @@ +/** + * Internal dependencies + */ +import { getPxFromCssUnit } from '../parse-css-unit-to-px'; + +describe( 'getPxFromCssUnit', () => { + // Absolute units + it( 'test px return px unit', () => { + expect( getPxFromCssUnit( '25px' ) ).toBe( '25px' ); + } ); + + it( 'test numeric float return px unit', () => { + expect( getPxFromCssUnit( '25.5' ) ).toBe( '26px' ); + } ); + + it( 'test cm return px unit', () => { + expect( getPxFromCssUnit( '1cm' ) ).toBe( '38px' ); + } ); + + it( 'test mm return px unit', () => { + expect( getPxFromCssUnit( '10mm' ) ).toBe( '38px' ); + } ); + + it( 'test in return px unit', () => { + expect( getPxFromCssUnit( '1in' ) ).toBe( '96px' ); + } ); + + it( 'test pt return px unit', () => { + expect( getPxFromCssUnit( '12pt' ) ).toBe( '16px' ); + } ); + + it( 'test pc return px unit', () => { + expect( getPxFromCssUnit( '1pc' ) ).toBe( '16px' ); + } ); + + it( 'test Q return px unit', () => { + expect( getPxFromCssUnit( '40Q' ) ).toBe( '38px' ); // 40 Q should be 1 cm + } ); + + // Relative units + it( 'test em return px unit', () => { + expect( getPxFromCssUnit( '2em', { fontSize: 10 } ) ).toBe( '20px' ); + } ); + + it( 'test rem return px unit', () => { + expect( getPxFromCssUnit( '2rem', { fontSize: 10 } ) ).toBe( '20px' ); + } ); + + it( 'test vw return px unit', () => { + expect( getPxFromCssUnit( '20vw', { width: 100 } ) ).toBe( '20px' ); + } ); + + it( 'test vh return px unit', () => { + expect( getPxFromCssUnit( '20vh', { height: 200 } ) ).toBe( '40px' ); + } ); + + it( 'test vmin return px unit', () => { + expect( + getPxFromCssUnit( '20vmin', { height: 200, width: 100 } ) + ).toBe( '20px' ); + } ); + + it( 'test vmax return px unit', () => { + expect( + getPxFromCssUnit( '20vmax', { height: 200, width: 100 } ) + ).toBe( '40px' ); + } ); + + it( 'test lh return px unit', () => { + expect( getPxFromCssUnit( '20lh', { lineHeight: 2 } ) ).toBe( '40px' ); + } ); + + it( 'test % return px unit', () => { + expect( + getPxFromCssUnit( '120%', { + height: 200, + width: 100, + fontSize: 10, + type: 'font', + } ) + ).toBe( '12px' ); + } ); + + // Function units + it( 'test min() return px unit', () => { + expect( getPxFromCssUnit( 'min(20px, 25px)' ) ).toBe( '20px' ); + } ); + + it( 'test min() function with many arguments return px unit', () => { + expect( getPxFromCssUnit( 'min(20px, 9px, 12pt, 25px)' ) ).toBe( + '9px' + ); + } ); + + it( 'test max() return px unit', () => { + expect( getPxFromCssUnit( 'max(20px, 25px)' ) ).toBe( '25px' ); + } ); + + it( 'test clamp() lower return px unit', () => { + expect( getPxFromCssUnit( 'clamp(10px, 9px, 25px)' ) ).toBe( '10px' ); + } ); + + it( 'test clamp() upper return px unit', () => { + expect( getPxFromCssUnit( 'clamp(10px, 35px, 25px)' ) ).toBe( '25px' ); + } ); + + it( 'test clamp() middle return px unit', () => { + expect( getPxFromCssUnit( 'clamp(10px, 15px, 25px)' ) ).toBe( '15px' ); + } ); + + it( 'test nested max min function return px unit', () => { + expect( getPxFromCssUnit( 'min(max(20px,25px), 35px)' ) ).toBe( + '25px' + ); + } ); + + it( 'test nested min max function return px unit', () => { + expect( getPxFromCssUnit( 'max(min(20px,25px), 35px)' ) ).toBe( + '35px' + ); + } ); + + it( 'test calculate function return px unit', () => { + expect( getPxFromCssUnit( '10px + 25px' ) ).toBe( '35px' ); + } ); + + it( 'test calc(10px + 25px) function return px unit', () => { + expect( getPxFromCssUnit( 'calc(10px + 25px)' ) ).toBe( '35px' ); + } ); + + it( 'test calc( number * cssUnit ) return px unit', () => { + expect( getPxFromCssUnit( 'calc( 2 * 20px)' ) ).toBe( '40px' ); + } ); + + it( 'test calc(25px - 10px) function return px unit', () => { + expect( getPxFromCssUnit( 'calc(25px - 10px)' ) ).toBe( '15px' ); + } ); + + it( 'test min(10px + 25px, 55pt) function return px unit', () => { + expect( getPxFromCssUnit( 'min(10px + 25px, 55pt)' ) ).toBe( '35px' ); + } ); + + it( 'test calc(12vw * 10px) function return px unit', () => { + expect( getPxFromCssUnit( 'calc(12vw * 10px)' ) ).toBe( '450px' ); + } ); + + it( 'test calc(42vw / 10px) function return px unit', () => { + expect( getPxFromCssUnit( 'calc(45vw / 10px)' ) ).toBe( '17px' ); + } ); + + it( 'test empty string', () => { + expect( getPxFromCssUnit( '' ) ).toBe( null ); + } ); + + it( 'test undefined string', () => { + expect( getPxFromCssUnit( undefined ) ).toBe( null ); + } ); + it( 'test integer string', () => { + expect( getPxFromCssUnit( 123 ) ).toBe( '123px' ); + } ); + + it( 'test float string', () => { + expect( getPxFromCssUnit( 123.456 ) ).toBe( '123px' ); + } ); + + it( 'test text string', () => { + expect( getPxFromCssUnit( 'abc' ) ).toBe( null ); + } ); + + it( 'test not non function return null', () => { + expect( getPxFromCssUnit( 'abc + num' ) ).toBe( null ); + } ); + + it( 'test not a fishy function return null', () => { + expect( getPxFromCssUnit( 'console.log("howdy"); + 10px' ) ).toBe( + null + ); + } ); + + it( 'test not a typo function return null', () => { + expect( getPxFromCssUnit( 'calc(12vw * 10px' ) ).toBe( null ); + } ); +} ); From 5bd0b40229daf533bb42d57558da03f75db3bfc0 Mon Sep 17 00:00:00 2001 From: George Mamadashvili <georgemamadashvili@gmail.com> Date: Sat, 4 Sep 2021 07:03:04 +0000 Subject: [PATCH 155/214] CustomSelectControl: Add describedBy fallback (#34385) --- .../src/custom-select-control/index.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/components/src/custom-select-control/index.js b/packages/components/src/custom-select-control/index.js index 560ac128218a34..0c40f485594e75 100644 --- a/packages/components/src/custom-select-control/index.js +++ b/packages/components/src/custom-select-control/index.js @@ -81,10 +81,18 @@ export default function CustomSelectControl( { stateReducer, } ); - const controlDescribedBy = describedBy - ? describedBy - : // translators: %s: The selected option. - sprintf( __( 'Currently selected: %s' ), selectedItem?.name ); + function getDescribedBy() { + if ( describedBy ) { + return describedBy; + } + + if ( ! selectedItem ) { + return __( 'No selection' ); + } + + // translators: %s: The selected option. + return sprintf( __( 'Currently selected: %s' ), selectedItem.name ); + } const menuProps = getMenuProps( { className: 'components-custom-select-control__menu', @@ -129,7 +137,7 @@ export default function CustomSelectControl( { 'aria-labelledby': undefined, className: 'components-custom-select-control__button', isSmall: true, - describedBy: controlDescribedBy, + describedBy: getDescribedBy(), } ) } > { itemToString( selectedItem ) } From 3116f9ce98ca1723d5962a84443b9d94abe4f6f0 Mon Sep 17 00:00:00 2001 From: Doug Wollison <doug@wollison.net> Date: Sat, 4 Sep 2021 05:28:18 -0400 Subject: [PATCH 156/214] Expose ThemeSupportCheck component (#20506) --- packages/editor/src/components/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index 50dfd6885166d2..89a7d1ab010353 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -58,6 +58,7 @@ export { default as PostVisibility } from './post-visibility'; export { default as PostVisibilityLabel } from './post-visibility/label'; export { default as PostVisibilityCheck } from './post-visibility/check'; export { default as TableOfContents } from './table-of-contents'; +export { default as ThemeSupportCheck } from './theme-support-check'; export { default as UnsavedChangesWarning } from './unsaved-changes-warning'; export { default as WordCount } from './word-count'; From 63ffde2812ca5de8b764fd525575212f46ce6d70 Mon Sep 17 00:00:00 2001 From: Glen Davies <glendaviesnz@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:30:32 +1200 Subject: [PATCH 157/214] Remove redundant css selector (#34277) Co-authored-by: Glen Davies <glen.davies@a8c.com> --- packages/block-library/src/gallery/save.js | 2 +- packages/block-library/src/gallery/style.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/gallery/save.js b/packages/block-library/src/gallery/save.js index bb1fa63a1ba594..77afff7f279847 100644 --- a/packages/block-library/src/gallery/save.js +++ b/packages/block-library/src/gallery/save.js @@ -20,7 +20,7 @@ export default function saveWithInnerBlocks( { attributes } ) { const { caption, columns, imageCrop } = attributes; - const className = classnames( 'blocks-gallery-grid', 'has-nested-images', { + const className = classnames( 'has-nested-images', { [ `columns-${ columns }` ]: columns !== undefined, [ `columns-default` ]: columns === undefined, 'is-cropped': imageCrop, diff --git a/packages/block-library/src/gallery/style.scss b/packages/block-library/src/gallery/style.scss index 30450edbc7cc6d..df7cf0634405eb 100644 --- a/packages/block-library/src/gallery/style.scss +++ b/packages/block-library/src/gallery/style.scss @@ -2,7 +2,7 @@ @import "./deprecated.scss"; // Styles for current version of gallery block. -.wp-block-gallery.blocks-gallery-grid.has-nested-images { +.wp-block-gallery.has-nested-images { display: flex; flex-wrap: wrap; // Need bogus :not(#individual-image) to override long :not() From f0e66a18edc16c97136736dd08da5cf94a7ca138 Mon Sep 17 00:00:00 2001 From: tellthemachines <tellthemachines@users.noreply.github.com> Date: Mon, 6 Sep 2021 10:32:55 +1000 Subject: [PATCH 158/214] Fix Page List styles inside responsive Navigation. (#34517) --- packages/block-library/src/navigation/style.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index eef14229835402..379ac1ee403e72 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -517,7 +517,8 @@ border: none; } - .wp-block-navigation-item { + .wp-block-navigation-item, + .wp-block-page-list { flex-direction: column; align-items: flex-start; } From 539cea09999f4d1f319a2a4e8052d1bf91b2c018 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Mon, 6 Sep 2021 13:09:22 +0800 Subject: [PATCH 159/214] Add undo redo buttons in navigation editor (#34533) * Add undo/redo buttons * Tidy up responsive styles * Update package lock --- package-lock.json | 1 + packages/edit-navigation/package.json | 1 + .../src/components/header/index.js | 22 ++++++++-- .../src/components/header/redo-button.js | 26 ++++++++++++ .../src/components/header/style.scss | 40 ++++++++++++++++--- .../src/components/header/undo-button.js | 26 ++++++++++++ 6 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 packages/edit-navigation/src/components/header/redo-button.js create mode 100644 packages/edit-navigation/src/components/header/undo-button.js diff --git a/package-lock.json b/package-lock.json index 9753141aae81ce..b7f4644029528d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18416,6 +18416,7 @@ "@wordpress/icons": "file:packages/icons", "@wordpress/interface": "file:packages/interface", "@wordpress/keyboard-shortcuts": "file:packages/keyboard-shortcuts", + "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/media-utils": "file:packages/media-utils", "@wordpress/notices": "file:packages/notices", "@wordpress/url": "file:packages/url", diff --git a/packages/edit-navigation/package.json b/packages/edit-navigation/package.json index ba871b960dfa72..caa7aeb9b90427 100644 --- a/packages/edit-navigation/package.json +++ b/packages/edit-navigation/package.json @@ -45,6 +45,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/interface": "file:../interface", "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", "@wordpress/media-utils": "file:../media-utils", "@wordpress/notices": "file:../notices", "@wordpress/url": "file:../url", diff --git a/packages/edit-navigation/src/components/header/index.js b/packages/edit-navigation/src/components/header/index.js index 10a08fddd6ac00..228b53b520ff8e 100644 --- a/packages/edit-navigation/src/components/header/index.js +++ b/packages/edit-navigation/src/components/header/index.js @@ -1,7 +1,9 @@ /** * WordPress dependencies */ +import { NavigableToolbar } from '@wordpress/block-editor'; import { DropdownMenu } from '@wordpress/components'; +import { useViewportMatch } from '@wordpress/compose'; import { PinnedItems } from '@wordpress/interface'; import { __, sprintf } from '@wordpress/i18n'; import { decodeEntities } from '@wordpress/html-entities'; @@ -10,6 +12,8 @@ import { decodeEntities } from '@wordpress/html-entities'; * Internal dependencies */ import SaveButton from './save-button'; +import UndoButton from './undo-button'; +import RedoButton from './redo-button'; import MenuSwitcher from '../menu-switcher'; import { useMenuEntityProp } from '../../hooks'; @@ -21,6 +25,7 @@ export default function Header( { isPending, navigationPost, } ) { + const isMediumViewport = useViewportMatch( 'medium' ); const [ menuName ] = useMenuEntityProp( 'name', selectedMenuId ); let actionHeaderText; @@ -39,9 +44,20 @@ export default function Header( { return ( <div className="edit-navigation-header"> - <h1 className="edit-navigation-header__title"> - { __( 'Navigation' ) } - </h1> + { isMediumViewport && ( + <div className="edit-navigation-header__toolbar-wrapper"> + <h1 className="edit-navigation-header__title"> + { __( 'Navigation' ) } + </h1> + <NavigableToolbar + className="edit-navigation-header__toolbar" + aria-label={ __( 'Document tools' ) } + > + <UndoButton /> + <RedoButton /> + </NavigableToolbar> + </div> + ) } <h2 className="edit-navigation-header__subtitle"> { isMenuSelected && decodeEntities( actionHeaderText ) } </h2> diff --git a/packages/edit-navigation/src/components/header/redo-button.js b/packages/edit-navigation/src/components/header/redo-button.js new file mode 100644 index 00000000000000..30827e9a6823e7 --- /dev/null +++ b/packages/edit-navigation/src/components/header/redo-button.js @@ -0,0 +1,26 @@ +/** + * WordPress dependencies + */ +import { __, isRTL } from '@wordpress/i18n'; +import { ToolbarButton } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { redo as redoIcon, undo as undoIcon } from '@wordpress/icons'; +import { displayShortcut } from '@wordpress/keycodes'; +import { store as coreStore } from '@wordpress/core-data'; + +export default function RedoButton() { + const hasRedo = useSelect( ( select ) => select( coreStore ).hasRedo() ); + const { redo } = useDispatch( coreStore ); + return ( + <ToolbarButton + icon={ ! isRTL() ? redoIcon : undoIcon } + label={ __( 'Redo' ) } + shortcut={ displayShortcut.primaryShift( 'z' ) } + // If there are no undo levels we don't want to actually disable this + // button, because it will remove focus for keyboard users. + // See: https://github.com/WordPress/gutenberg/issues/3486 + aria-disabled={ ! hasRedo } + onClick={ hasRedo ? redo : undefined } + /> + ); +} diff --git a/packages/edit-navigation/src/components/header/style.scss b/packages/edit-navigation/src/components/header/style.scss index aec34010fcd828..bc940b93a14bc2 100644 --- a/packages/edit-navigation/src/components/header/style.scss +++ b/packages/edit-navigation/src/components/header/style.scss @@ -5,22 +5,50 @@ padding: $grid-unit-15 $grid-unit-30 $grid-unit-15 20px; } +.edit-navigation-header__toolbar-wrapper { + display: flex; + align-items: center; + justify-content: center; +} + .edit-navigation-header__title { font-size: 20px; padding: 0; margin: 0 20px 0 0; } +.edit-navigation-header__toolbar { + border: none; + + // The Toolbar component adds different styles to buttons, so we reset them + // here to the original button styles + // Specificity bump needed to offset https://github.com/WordPress/gutenberg/blob/8ea29cb04412c80c9adf7c1db0e816d6a0ac1232/packages/components/src/toolbar/style.scss#L76 + > .components-button.has-icon.has-icon.has-icon, + > .components-dropdown > .components-button.has-icon.has-icon { + height: $button-size; + min-width: $button-size; + padding: 6px; + + &.is-pressed { + background: $gray-900; + } + + &:focus:not(:disabled) { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color), inset 0 0 0 $border-width $white; + outline: 1px solid transparent; + } + + &::before { + display: none; + } + } +} + .edit-navigation-header__subtitle { - display: none; + display: block; margin: 0; font-size: 15px; font-weight: normal; - - // Only display on larger screens. - @include break-small() { - display: block; - } } .edit-navigation-header__actions { diff --git a/packages/edit-navigation/src/components/header/undo-button.js b/packages/edit-navigation/src/components/header/undo-button.js new file mode 100644 index 00000000000000..82b9caae945560 --- /dev/null +++ b/packages/edit-navigation/src/components/header/undo-button.js @@ -0,0 +1,26 @@ +/** + * WordPress dependencies + */ +import { __, isRTL } from '@wordpress/i18n'; +import { ToolbarButton } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { undo as undoIcon, redo as redoIcon } from '@wordpress/icons'; +import { displayShortcut } from '@wordpress/keycodes'; +import { store as coreStore } from '@wordpress/core-data'; + +export default function UndoButton() { + const hasUndo = useSelect( ( select ) => select( coreStore ).hasUndo() ); + const { undo } = useDispatch( coreStore ); + return ( + <ToolbarButton + icon={ ! isRTL() ? undoIcon : redoIcon } + label={ __( 'Undo' ) } + shortcut={ displayShortcut.primary( 'z' ) } + // If there are no undo levels we don't want to actually disable this + // button, because it will remove focus for keyboard users. + // See: https://github.com/WordPress/gutenberg/issues/3486 + aria-disabled={ ! hasUndo } + onClick={ hasUndo ? undo : undefined } + /> + ); +} From 44d1d3237948ef7c8a4e9a4f68773f89bf1b6bf1 Mon Sep 17 00:00:00 2001 From: Matthew Reishus <mreishus@users.noreply.github.com> Date: Mon, 6 Sep 2021 00:49:39 -0500 Subject: [PATCH 160/214] Fix Block Settings sidebar unexpectedly collapsing (#34543) --- packages/customize-widgets/src/controls/inspector-section.js | 3 +++ packages/customize-widgets/src/controls/sidebar-section.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/customize-widgets/src/controls/inspector-section.js b/packages/customize-widgets/src/controls/inspector-section.js index b6cb32ccf9c8d9..48b574a65ea15a 100644 --- a/packages/customize-widgets/src/controls/inspector-section.js +++ b/packages/customize-widgets/src/controls/inspector-section.js @@ -16,6 +16,9 @@ export default function getInspectorSection() { 'customize-widgets-layout__inspector' ); } + isContextuallyActive() { + return this.active(); + } onChangeExpanded( expanded, args ) { super.onChangeExpanded( expanded, args ); diff --git a/packages/customize-widgets/src/controls/sidebar-section.js b/packages/customize-widgets/src/controls/sidebar-section.js index a30f4c242b0350..e78c47f94f3515 100644 --- a/packages/customize-widgets/src/controls/sidebar-section.js +++ b/packages/customize-widgets/src/controls/sidebar-section.js @@ -36,6 +36,9 @@ export default function getSidebarSection() { 'customize-widgets__sidebar-section' ); } + isContextuallyActive() { + return this.active(); + } hasSubSectionOpened() { return this.inspector.expanded(); } From faf3e9851122959fd6db02995ddc02d8e20dc79a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <4710635+ellatrix@users.noreply.github.com> Date: Mon, 6 Sep 2021 10:40:44 +0300 Subject: [PATCH 161/214] Linting: remove global event listener warning (#34528) --- packages/admin-manifest/src/index.js | 1 - .../src/ui/utils/use-responsive-value.ts | 3 - packages/eslint-plugin/configs/custom.js | 2 - .../__tests__/no-global-event-listener.js | 70 ------------------- .../rules/no-global-event-listener.js | 35 ---------- 5 files changed, 111 deletions(-) delete mode 100644 packages/eslint-plugin/rules/__tests__/no-global-event-listener.js delete mode 100644 packages/eslint-plugin/rules/no-global-event-listener.js diff --git a/packages/admin-manifest/src/index.js b/packages/admin-manifest/src/index.js index 38fa248c6d9f19..a19032ac4565c6 100644 --- a/packages/admin-manifest/src/index.js +++ b/packages/admin-manifest/src/index.js @@ -101,7 +101,6 @@ function getAdminBarColors() { }; } -// eslint-disable-next-line @wordpress/no-global-event-listener window.addEventListener( 'load', () => { if ( ! ( 'serviceWorker' in window.navigator ) ) { return; diff --git a/packages/components/src/ui/utils/use-responsive-value.ts b/packages/components/src/ui/utils/use-responsive-value.ts index da284a6510a385..d8b324b097a525 100644 --- a/packages/components/src/ui/utils/use-responsive-value.ts +++ b/packages/components/src/ui/utils/use-responsive-value.ts @@ -41,14 +41,11 @@ export const useBreakpointIndex = ( onResize(); if ( typeof window !== 'undefined' ) { - // Disable reason: We don't really care about what document we listen to, we just want to know that we're resizing. - /* eslint-disable @wordpress/no-global-event-listener */ window.addEventListener( 'resize', onResize ); } return () => { if ( typeof window !== 'undefined' ) { window.removeEventListener( 'resize', onResize ); - /* eslint-enable @wordpress/no-global-event-listener */ } }; }, [ value ] ); diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js index 8964c5e5b82fc7..98417b4122b334 100644 --- a/packages/eslint-plugin/configs/custom.js +++ b/packages/eslint-plugin/configs/custom.js @@ -6,7 +6,6 @@ module.exports = { '@wordpress/no-unguarded-get-range-at': 'error', '@wordpress/no-global-active-element': 'error', '@wordpress/no-global-get-selection': 'error', - '@wordpress/no-global-event-listener': 'warn', '@wordpress/no-unsafe-wp-apis': 'error', }, overrides: [ @@ -25,7 +24,6 @@ module.exports = { rules: { '@wordpress/no-global-active-element': 'off', '@wordpress/no-global-get-selection': 'off', - '@wordpress/no-global-event-listener': 'off', }, }, ], diff --git a/packages/eslint-plugin/rules/__tests__/no-global-event-listener.js b/packages/eslint-plugin/rules/__tests__/no-global-event-listener.js deleted file mode 100644 index b7434cf5b62c93..00000000000000 --- a/packages/eslint-plugin/rules/__tests__/no-global-event-listener.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * External dependencies - */ -import { RuleTester } from 'eslint'; - -/** - * Internal dependencies - */ -import rule from '../no-global-event-listener'; - -const ruleTester = new RuleTester( { - parserOptions: { - ecmaVersion: 6, - }, -} ); - -ruleTester.run( 'no-global-event-listener', rule, { - valid: [ - { - code: 'ownerDocument.addEventListener();', - }, - { - code: 'ownerDocument.removeEventListener();', - }, - { - code: 'defaultView.addEventListener();', - }, - { - code: 'defaultView.removeEventListener();', - }, - ], - invalid: [ - { - code: 'document.addEventListener();', - errors: [ - { - message: - 'Avoid using (add|remove)EventListener with globals. Use `ownerDocument` or `ownerDocument.defaultView` on a node ref instead.', - }, - ], - }, - { - code: 'document.removeEventListener();', - errors: [ - { - message: - 'Avoid using (add|remove)EventListener with globals. Use `ownerDocument` or `ownerDocument.defaultView` on a node ref instead.', - }, - ], - }, - { - code: 'window.addEventListener();', - errors: [ - { - message: - 'Avoid using (add|remove)EventListener with globals. Use `ownerDocument` or `ownerDocument.defaultView` on a node ref instead.', - }, - ], - }, - { - code: 'window.removeEventListener();', - errors: [ - { - message: - 'Avoid using (add|remove)EventListener with globals. Use `ownerDocument` or `ownerDocument.defaultView` on a node ref instead.', - }, - ], - }, - ], -} ); diff --git a/packages/eslint-plugin/rules/no-global-event-listener.js b/packages/eslint-plugin/rules/no-global-event-listener.js deleted file mode 100644 index 6fa73eeff3c2a8..00000000000000 --- a/packages/eslint-plugin/rules/no-global-event-listener.js +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = { - meta: { - type: 'problem', - schema: [], - }, - create( context ) { - return { - CallExpression( node ) { - const { callee } = node; - const { object, property } = callee; - - if ( ! object || ! property ) { - return; - } - - if ( object.name !== 'document' && object.name !== 'window' ) { - return; - } - - if ( - property.name !== 'addEventListener' && - property.name !== 'removeEventListener' - ) { - return; - } - - context.report( { - node, - message: - 'Avoid using (add|remove)EventListener with globals. Use `ownerDocument` or `ownerDocument.defaultView` on a node ref instead.', - } ); - }, - }; - }, -}; From ff2c43886b6bdb004ad58d16f8f982c7a811d68f Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Mon, 6 Sep 2021 13:01:37 +0200 Subject: [PATCH 162/214] Try: Title block gap. (#34570) --- packages/edit-post/src/components/visual-editor/style.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/edit-post/src/components/visual-editor/style.scss b/packages/edit-post/src/components/visual-editor/style.scss index aebef09c4eac37..b93af3ee80c715 100644 --- a/packages/edit-post/src/components/visual-editor/style.scss +++ b/packages/edit-post/src/components/visual-editor/style.scss @@ -44,8 +44,9 @@ margin-right: auto; // Margins between the title and the first block, or appender, do not collapse. - // By explicitly setting this to zero, we avoid "double margin". - margin-bottom: 0; + // However in that support block gap, the first items in post content do not have a top margin. + // By leveraging the gap variable, with a fallback of zero, we handle both cases. + margin-bottom: var(--wp--style--block-gap, 0); } } From 22457bbab2b71a26dc3dd9fd99dfb14bb3429790 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 6 Sep 2021 13:17:23 +0100 Subject: [PATCH 163/214] Allow themes with theme.json to opt-out of block gap styles (#34491) --- lib/block-supports/layout.php | 29 ++++++++++++------- lib/class-wp-theme-json-gutenberg.php | 6 +++- lib/theme.json | 2 +- packages/block-editor/src/layouts/flex.js | 10 ++++++- packages/block-editor/src/layouts/flow.js | 15 +++++++--- .../class-wp-theme-json-schema-v0-test.php | 4 +-- phpunit/class-wp-theme-json-test.php | 13 +++++---- 7 files changed, 55 insertions(+), 24 deletions(-) diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 56d893ae7d3dfb..92763e010d7756 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -28,12 +28,13 @@ function gutenberg_register_layout_support( $block_type ) { /** * Generates the CSS corresponding to the provided layout. * - * @param string $selector CSS selector. - * @param array $layout Layout object. + * @param string $selector CSS selector. + * @param array $layout Layout object. + * @param boolean $has_block_gap_support Whether the theme has support for the block gap. * * @return string CSS style. */ -function gutenberg_get_layout_style( $selector, $layout ) { +function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support = false ) { $layout_type = isset( $layout['type'] ) ? $layout['type'] : 'default'; $style = ''; @@ -63,11 +64,17 @@ function gutenberg_get_layout_style( $selector, $layout ) { $style .= "$selector .alignleft { float: left; margin-right: 2em; }"; $style .= "$selector .alignright { float: right; margin-left: 2em; }"; - $style .= "$selector > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }"; + if ( $has_block_gap_support ) { + $style .= "$selector > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }"; + } } elseif ( 'flex' === $layout_type ) { $style = "$selector {"; $style .= 'display: flex;'; - $style .= 'gap: var( --wp--style--block-gap, 0.5em );'; + if ( $has_block_gap_support ) { + $style .= 'gap: var( --wp--style--block-gap, 0.5em );'; + } else { + $style .= 'gap: 0.5em;'; + } $style .= 'flex-wrap: wrap;'; $style .= 'align-items: center;'; $style .= '}'; @@ -93,11 +100,13 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { return $block_content; } - $default_block_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() ); - $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $default_block_layout; + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( array(), 'theme' ); + $theme_settings = $tree->get_settings(); + $default_layout = _wp_array_get( $theme_settings, array( 'layout' ) ); + $has_block_gap_support = isset( $theme_settings['spacing']['blockGap'] ) ? null !== $theme_settings['spacing']['blockGap'] : false; + $default_block_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() ); + $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $default_block_layout; if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] ) { - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( array(), 'theme' ); - $default_layout = _wp_array_get( $tree->get_settings(), array( 'layout' ) ); if ( ! $default_layout ) { return $block_content; } @@ -105,7 +114,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { } $id = uniqid(); - $style = gutenberg_get_layout_style( ".wp-container-$id", $used_layout ); + $style = gutenberg_get_layout_style( ".wp-container-$id", $used_layout, $has_block_gap_support ); // This assumes the hook only applies to blocks with a single wrapper. // I think this is a reasonable limitation for that particular hook. $content = preg_replace( diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 474294caa2a218..b3f2cbe6fd2c34 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -103,6 +103,7 @@ class WP_Theme_JSON_Gutenberg { 'customMargin' => null, 'customPadding' => null, 'units' => null, + 'blockGap' => null, ), 'typography' => array( 'customFontSize' => null, @@ -818,7 +819,10 @@ private function get_block_classes( $style_nodes ) { $block_rules .= self::to_ruleset( $selector, $declarations ); if ( self::ROOT_BLOCK_SELECTOR === $selector ) { - $block_rules .= '.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }'; + $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; + if ( $has_block_gap_support ) { + $block_rules .= '.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }'; + } } } diff --git a/lib/theme.json b/lib/theme.json index 5d952b381ef932..37ccaf0c010df8 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -211,7 +211,7 @@ ] }, "spacing": { - "blockGap": false, + "blockGap": null, "customMargin": false, "customPadding": false, "units": [ "px", "em", "rem", "vh", "vw", "%" ] diff --git a/packages/block-editor/src/layouts/flex.js b/packages/block-editor/src/layouts/flex.js index d130468b93ceec..268674ee3e503e 100644 --- a/packages/block-editor/src/layouts/flex.js +++ b/packages/block-editor/src/layouts/flex.js @@ -7,6 +7,7 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { appendSelectors } from './utils'; +import useSetting from '../components/use-setting'; export default { name: 'flex', @@ -18,11 +19,18 @@ export default { }, save: function FlexLayoutStyle( { selector } ) { + const blockGapSupport = useSetting( 'spacing.blockGap' ); + const hasBlockGapStylesSupport = blockGapSupport !== null; + return ( <style>{ ` ${ appendSelectors( selector ) } { display: flex; - gap: var( --wp--style--block-gap, 0.5em ); + gap: ${ + hasBlockGapStylesSupport + ? 'var( --wp--style--block-gap, 0.5em )' + : '0.5em' + }; flex-wrap: wrap; align-items: center; } diff --git a/packages/block-editor/src/layouts/flow.js b/packages/block-editor/src/layouts/flow.js index 2a57f34822d165..3e0fc374ee5483 100644 --- a/packages/block-editor/src/layouts/flow.js +++ b/packages/block-editor/src/layouts/flow.js @@ -104,6 +104,8 @@ export default { save: function DefaultLayoutStyle( { selector, layout = {} } ) { const { contentSize, wideSize } = layout; + const blockGapSupport = useSetting( 'spacing.blockGap' ); + const hasBlockGapStylesSupport = blockGapSupport !== null; let style = !! contentSize || !! wideSize @@ -135,12 +137,17 @@ export default { margin-left: 2em; } - ${ appendSelectors( selector, '> * + *' ) } { - margin-top: var( --wp--style--block-gap ); - margin-bottom: 0; - } `; + if ( hasBlockGapStylesSupport ) { + style += ` + ${ appendSelectors( selector, '> * + *' ) } { + margin-top: var( --wp--style--block-gap ); + margin-bottom: 0; + } + `; + } + return <style>{ style }</style>; }, diff --git a/phpunit/class-wp-theme-json-schema-v0-test.php b/phpunit/class-wp-theme-json-schema-v0-test.php index 442e917220c58e..d3f8bce0b2b530 100644 --- a/phpunit/class-wp-theme-json-schema-v0-test.php +++ b/phpunit/class-wp-theme-json-schema-v0-test.php @@ -471,11 +471,11 @@ function test_get_stylesheet() { ); $this->assertEquals( - 'body{--wp--preset--color--grey: grey;--wp--preset--font-family--small: 14px;--wp--preset--font-family--big: 41px;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}body{color: var(--wp--preset--color--grey);}.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }a{color: #111;}h1{font-size: 1em;}h2{font-size: 2em;}.wp-block-group{padding-top: 12px;padding-bottom: 24px;}.wp-block-group a{color: #333;}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{color: #222;}.wp-block-post-title{font-size: 5em;}.wp-block-post-title a{color: #555;}.wp-block-query-title{font-size: 5em;}.wp-block-query-title a{color: #555;}.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}', + 'body{--wp--preset--color--grey: grey;--wp--preset--font-family--small: 14px;--wp--preset--font-family--big: 41px;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}body{color: var(--wp--preset--color--grey);}a{color: #111;}h1{font-size: 1em;}h2{font-size: 2em;}.wp-block-group{padding-top: 12px;padding-bottom: 24px;}.wp-block-group a{color: #333;}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{color: #222;}.wp-block-post-title{font-size: 5em;}.wp-block-post-title a{color: #555;}.wp-block-query-title{font-size: 5em;}.wp-block-query-title a{color: #555;}.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}', $theme_json->get_stylesheet() ); $this->assertEquals( - 'body{color: var(--wp--preset--color--grey);}.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }a{color: #111;}h1{font-size: 1em;}h2{font-size: 2em;}.wp-block-group{padding-top: 12px;padding-bottom: 24px;}.wp-block-group a{color: #333;}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{color: #222;}.wp-block-post-title{font-size: 5em;}.wp-block-post-title a{color: #555;}.wp-block-query-title{font-size: 5em;}.wp-block-query-title a{color: #555;}.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}', + 'body{color: var(--wp--preset--color--grey);}a{color: #111;}h1{font-size: 1em;}h2{font-size: 2em;}.wp-block-group{padding-top: 12px;padding-bottom: 24px;}.wp-block-group a{color: #333;}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{color: #222;}.wp-block-post-title{font-size: 5em;}.wp-block-post-title a{color: #555;}.wp-block-query-title{font-size: 5em;}.wp-block-query-title a{color: #555;}.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}', $theme_json->get_stylesheet( 'block_styles' ) ); $this->assertEquals( diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 5fde40de86e7cb..d88adc7f384a3e 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -225,11 +225,11 @@ function test_get_stylesheet_support_for_shorthand_and_longhand_values() { ); $this->assertEquals( - '.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }.wp-block-group{border-radius: 10px;margin: 1em;padding: 24px;}.wp-block-image{border-top-left-radius: 10px;border-bottom-right-radius: 1em;margin-bottom: 30px;padding-top: 15px;}', + '.wp-block-group{border-radius: 10px;margin: 1em;padding: 24px;}.wp-block-image{border-top-left-radius: 10px;border-bottom-right-radius: 1em;margin-bottom: 30px;padding-top: 15px;}', $theme_json->get_stylesheet() ); $this->assertEquals( - '.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }.wp-block-group{border-radius: 10px;margin: 1em;padding: 24px;}.wp-block-image{border-top-left-radius: 10px;border-bottom-right-radius: 1em;margin-bottom: 30px;padding-top: 15px;}', + '.wp-block-group{border-radius: 10px;margin: 1em;padding: 24px;}.wp-block-image{border-top-left-radius: 10px;border-bottom-right-radius: 1em;margin-bottom: 30px;padding-top: 15px;}', $theme_json->get_stylesheet( 'block_styles' ) ); } @@ -260,6 +260,9 @@ function test_get_stylesheet() { ), ), ), + 'spacing' => array( + 'blockGap' => false, + ), 'misc' => 'value', 'blocks' => array( 'core/group' => array( @@ -423,11 +426,11 @@ function test_get_stylesheet_preset_rules_come_after_block_rules() { ); $this->assertEquals( - '.wp-block-group{--wp--preset--color--grey: grey;}.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }.wp-block-group{color: red;}.wp-block-group.has-grey-color{color: var(--wp--preset--color--grey) !important;}.wp-block-group.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.wp-block-group.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}', + '.wp-block-group{--wp--preset--color--grey: grey;}.wp-block-group{color: red;}.wp-block-group.has-grey-color{color: var(--wp--preset--color--grey) !important;}.wp-block-group.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.wp-block-group.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}', $theme_json->get_stylesheet() ); $this->assertEquals( - '.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }.wp-block-group{color: red;}.wp-block-group.has-grey-color{color: var(--wp--preset--color--grey) !important;}.wp-block-group.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.wp-block-group.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}', + '.wp-block-group{color: red;}.wp-block-group.has-grey-color{color: var(--wp--preset--color--grey) !important;}.wp-block-group.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.wp-block-group.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}', $theme_json->get_stylesheet( 'block_styles' ) ); } @@ -465,7 +468,7 @@ public function test_get_stylesheet_preset_values_are_marked_as_important() { ); $this->assertEquals( - 'body{--wp--preset--color--grey: grey;}.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }p{background-color: blue;color: red;font-size: 12px;line-height: 1.3;}.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}', + 'body{--wp--preset--color--grey: grey;}p{background-color: blue;color: red;font-size: 12px;line-height: 1.3;}.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}', $theme_json->get_stylesheet() ); } From 351e52c15ac5a356bec3e904689217cbbe1ccd93 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 6 Sep 2021 13:42:56 +0100 Subject: [PATCH 164/214] Update the minimum supported WordPress version to 5.7 (#34536) --- .github/ISSUE_TEMPLATE/Bug_report_mobile.md | 2 +- gutenberg.php | 6 +- lib/block-supports/align.php | 59 -------------------- lib/block-supports/custom-classname.php | 58 -------------------- lib/block-supports/generated-classname.php | 61 --------------------- lib/client-assets.php | 47 ---------------- lib/load.php | 3 - 7 files changed, 4 insertions(+), 232 deletions(-) delete mode 100644 lib/block-supports/align.php delete mode 100644 lib/block-supports/custom-classname.php delete mode 100644 lib/block-supports/generated-classname.php diff --git a/.github/ISSUE_TEMPLATE/Bug_report_mobile.md b/.github/ISSUE_TEMPLATE/Bug_report_mobile.md index 06b6fad1bb6bc3..8347a6649ea8d6 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report_mobile.md +++ b/.github/ISSUE_TEMPLATE/Bug_report_mobile.md @@ -44,7 +44,7 @@ the bug. --> ## WordPress information -- WordPress version: <!-- e.g. "5.6.0". Find this in Tools → Site Health → Info → WordPress --> +- WordPress version: <!-- e.g. "5.8.0". Find this in Tools → Site Health → Info → WordPress --> - Gutenberg version: <!-- e.g. "9.4.0" or "Not installed" --> - Are all plugins except Gutenberg deactivated? <!-- "Yes" or "No" --> - Are you using a default theme (e.g. Twenty Twenty-One)? <!-- "Yes" or "No" --> diff --git a/gutenberg.php b/gutenberg.php index bc6e9ec3fdda9e..47dd00c9a4b73f 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Requires at least: 5.6 + * Requires at least: 5.7 * Requires PHP: 5.6 * Version: 11.4.1 * Author: Gutenberg Team @@ -26,7 +26,7 @@ function gutenberg_wordpress_version_notice() { echo '<div class="error"><p>'; /* translators: %s: Minimum required version */ - printf( __( 'Gutenberg requires WordPress %s or later to function properly. Please upgrade WordPress before activating Gutenberg.', 'gutenberg' ), '5.6' ); + printf( __( 'Gutenberg requires WordPress %s or later to function properly. Please upgrade WordPress before activating Gutenberg.', 'gutenberg' ), '5.7' ); echo '</p></div>'; deactivate_plugins( array( 'gutenberg/gutenberg.php' ) ); @@ -64,7 +64,7 @@ function gutenberg_pre_init() { // Compare against major release versions (X.Y) rather than minor (X.Y.Z) // unless a minor release is the actual minimum requirement. WordPress reports // X.Y for its major releases. - if ( version_compare( $version, '5.6', '<' ) ) { + if ( version_compare( $version, '5.7', '<' ) ) { add_action( 'admin_notices', 'gutenberg_wordpress_version_notice' ); return; } diff --git a/lib/block-supports/align.php b/lib/block-supports/align.php deleted file mode 100644 index a33a7700a5d0b5..00000000000000 --- a/lib/block-supports/align.php +++ /dev/null @@ -1,59 +0,0 @@ -<?php -/** - * Align block support flag. - * - * @package gutenberg - */ - -/** - * Registers the align block attribute for block types that support it. - * - * @param WP_Block_Type $block_type Block Type. - */ -function gutenberg_register_alignment_support( $block_type ) { - $has_align_support = gutenberg_block_has_support( $block_type, array( 'align' ), false ); - if ( $has_align_support ) { - if ( ! $block_type->attributes ) { - $block_type->attributes = array(); - } - - if ( ! array_key_exists( 'align', $block_type->attributes ) ) { - $block_type->attributes['align'] = array( - 'type' => 'string', - 'enum' => array( 'left', 'center', 'right', 'wide', 'full', '' ), - ); - } - } -} - -/** - * Add CSS classes for block alignment to the incoming attributes array. - * This will be applied to the block markup in the front-end. - * - * @param WP_Block_Type $block_type Block Type. - * @param array $block_attributes Block attributes. - * - * @return array Block alignment CSS classes and inline styles. - */ -function gutenberg_apply_alignment_support( $block_type, $block_attributes ) { - $attributes = array(); - $has_align_support = gutenberg_block_has_support( $block_type, array( 'align' ), false ); - if ( $has_align_support ) { - $has_block_alignment = array_key_exists( 'align', $block_attributes ); - - if ( $has_block_alignment ) { - $attributes['class'] = sprintf( 'align%s', $block_attributes['align'] ); - } - } - - return $attributes; -} - -// Register the block support. -WP_Block_Supports::get_instance()->register( - 'align', - array( - 'register_attribute' => 'gutenberg_register_alignment_support', - 'apply' => 'gutenberg_apply_alignment_support', - ) -); diff --git a/lib/block-supports/custom-classname.php b/lib/block-supports/custom-classname.php deleted file mode 100644 index 612eaf30bd50ce..00000000000000 --- a/lib/block-supports/custom-classname.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php -/** - * Custom classname block support flag. - * - * @package gutenberg - */ - -/** - * Registers the custom classname block attribute for block types that support it. - * - * @param WP_Block_Type $block_type Block Type. - */ -function gutenberg_register_custom_classname_support( $block_type ) { - $has_custom_classname_support = gutenberg_block_has_support( $block_type, array( 'customClassName' ), true ); - - if ( $has_custom_classname_support ) { - if ( ! $block_type->attributes ) { - $block_type->attributes = array(); - } - - if ( ! array_key_exists( 'className', $block_type->attributes ) ) { - $block_type->attributes['className'] = array( - 'type' => 'string', - ); - } - } -} - -/** - * Add the custom classnames to the output. - * - * @param WP_Block_Type $block_type Block Type. - * @param array $block_attributes Block attributes. - * - * @return array Block CSS classes and inline styles. - */ -function gutenberg_apply_custom_classname_support( $block_type, $block_attributes ) { - $has_custom_classname_support = gutenberg_block_has_support( $block_type, array( 'customClassName' ), true ); - $attributes = array(); - if ( $has_custom_classname_support ) { - $has_custom_classnames = array_key_exists( 'className', $block_attributes ); - - if ( $has_custom_classnames ) { - $attributes['class'] = $block_attributes['className']; - } - } - - return $attributes; -} - -// Register the block support. -WP_Block_Supports::get_instance()->register( - 'custom-classname', - array( - 'register_attribute' => 'gutenberg_register_custom_classname_support', - 'apply' => 'gutenberg_apply_custom_classname_support', - ) -); diff --git a/lib/block-supports/generated-classname.php b/lib/block-supports/generated-classname.php deleted file mode 100644 index 8be75f6431fb2e..00000000000000 --- a/lib/block-supports/generated-classname.php +++ /dev/null @@ -1,61 +0,0 @@ -<?php -/** - * Generated classname block support flag. - * - * @package gutenberg - */ - -/** - * Get the generated classname from a given block name. - * - * @param string $block_name Block Name. - * @return string Generated classname. - */ -function gutenberg_get_block_default_classname( $block_name ) { - // Generated HTML classes for blocks follow the `wp-block-{name}` nomenclature. - // Blocks provided by WordPress drop the prefixes 'core/' or 'core-' (historically used in 'core-embed/'). - $classname = 'wp-block-' . preg_replace( - '/^core-/', - '', - str_replace( '/', '-', $block_name ) - ); - - /** - * Filters the default block className for server rendered blocks. - * - * @param string $class_name The current applied classname. - * @param string $block_name The block name. - */ - $classname = apply_filters( 'block_default_classname', $classname, $block_name ); - - return $classname; -} - -/** - * Add the generated classnames to the output. - * - * @param WP_Block_Type $block_type Block Type. - * - * @return array Block CSS classes and inline styles. - */ -function gutenberg_apply_generated_classname_support( $block_type ) { - $attributes = array(); - $has_generated_classname_support = gutenberg_block_has_support( $block_type, array( 'className' ), true ); - if ( $has_generated_classname_support ) { - $block_classname = gutenberg_get_block_default_classname( $block_type->name ); - - if ( $block_classname ) { - $attributes['class'] = $block_classname; - } - } - - return $attributes; -} - -// Register the block support. -WP_Block_Supports::get_instance()->register( - 'generated-classname', - array( - 'apply' => 'gutenberg_apply_generated_classname_support', - ) -); diff --git a/lib/client-assets.php b/lib/client-assets.php index 603576ed45f5f4..2f8d3ed48fa810 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -96,13 +96,6 @@ function gutenberg_override_script( $scripts, $handle, $src, $deps = array(), $v $scripts->set_translations( $handle, 'default' ); } - // Remove this check once the minimum supported WordPress version is at least 5.7. - if ( 'wp-i18n' === $handle ) { - $ltr = 'rtl' === _x( 'ltr', 'text direction', 'default' ) ? 'rtl' : 'ltr'; - $output = sprintf( "wp.i18n.setLocaleData( { 'text direction\u0004ltr': [ '%s' ] }, 'default' );", $ltr ); - $scripts->add_inline_script( 'wp-i18n', $output, 'after' ); - } - /* * Wp-editor module is exposed as window.wp.editor. * Problem: there is quite some code expecting window.wp.oldEditor object available under window.wp.editor. @@ -223,18 +216,6 @@ function gutenberg_register_vendor_scripts( $scripts ) { 'https://unpkg.com/react-dom@17.0.1/umd/react-dom' . $react_suffix . '.js', array( 'react' ) ); - - /* - * This script registration and the corresponding function should be removed - * removed once the plugin is updated to support WordPress 5.7.0 and newer. - */ - gutenberg_register_vendor_script( - $scripts, - 'object-fit-polyfill', - 'https://unpkg.com/objectFitPolyfill@2.3.5/dist/objectFitPolyfill.min.js', - array(), - '2.3.5' - ); } add_action( 'wp_default_scripts', 'gutenberg_register_vendor_scripts' ); @@ -797,31 +778,3 @@ function gutenberg_extend_block_editor_styles_html() { add_action( 'admin_footer-post.php', 'gutenberg_extend_block_editor_styles_html' ); add_action( 'admin_footer-post-new.php', 'gutenberg_extend_block_editor_styles_html' ); add_action( 'admin_footer-widgets.php', 'gutenberg_extend_block_editor_styles_html' ); - -/** - * Adds a polyfill for object-fit in environments which do not support it. - * - * The script registration occurs in `gutenberg_register_vendor_scripts`, which - * should be removed in coordination with this function. - * - * Remove this when the minimum supported version is WordPress 5.7 - * - * @see gutenberg_register_vendor_scripts - * @see https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit - * - * @since 9.1.0 - * - * @param WP_Scripts $scripts WP_Scripts object. - */ -function gutenberg_add_object_fit_polyfill( $scripts ) { - did_action( 'init' ) && $scripts->add_inline_script( - 'wp-polyfill', - wp_get_script_polyfill( - $scripts, - array( - '"objectFit" in document.documentElement.style' => 'object-fit-polyfill', - ) - ) - ); -} -add_action( 'wp_default_scripts', 'gutenberg_add_object_fit_polyfill', 20 ); diff --git a/lib/load.php b/lib/load.php index 096fe553d95e13..bf9d85e40e5e65 100644 --- a/lib/load.php +++ b/lib/load.php @@ -122,12 +122,9 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/global-styles.php'; require __DIR__ . '/pwa.php'; -require __DIR__ . '/block-supports/generated-classname.php'; require __DIR__ . '/block-supports/elements.php'; require __DIR__ . '/block-supports/colors.php'; -require __DIR__ . '/block-supports/align.php'; require __DIR__ . '/block-supports/typography.php'; -require __DIR__ . '/block-supports/custom-classname.php'; require __DIR__ . '/block-supports/border.php'; require __DIR__ . '/block-supports/layout.php'; require __DIR__ . '/block-supports/spacing.php'; From a2e180a45e3cccaa783bd0a6aa7ede738bd72990 Mon Sep 17 00:00:00 2001 From: Marco Ciampini <marco.ciampo@gmail.com> Date: Mon, 6 Sep 2021 16:14:16 +0200 Subject: [PATCH 165/214] Components: check if in browser env before calling `CSS.supports` (#34572) --- packages/components/CHANGELOG.md | 1 + packages/components/src/ui/utils/space.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 9b6ae2d6cc5dd7..90d083445fab2d 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -9,6 +9,7 @@ ### Bug Fix - Fixed RTL styles in `Flex` component ([#33729](https://github.com/WordPress/gutenberg/pull/33729)). +- Fixed unit test errors caused by `CSS.supports` being called in a non-browser environment ([#34572](https://github.com/WordPress/gutenberg/pull/34572)). ### Internal diff --git a/packages/components/src/ui/utils/space.ts b/packages/components/src/ui/utils/space.ts index d6a3748a5a0eca..b3f2c0a8b3e873 100644 --- a/packages/components/src/ui/utils/space.ts +++ b/packages/components/src/ui/utils/space.ts @@ -30,7 +30,8 @@ export function space( value?: SpaceInput ): string | undefined { // test if the input has a unit, was NaN, or was one of the named CSS values (like `auto`), in which case just use that value if ( - CSS.supports?.( 'margin', value.toString() ) || + ( typeof window !== 'undefined' && + window.CSS?.supports?.( 'margin', value.toString() ) ) || Number.isNaN( asInt ) ) { return value.toString(); From cee91360a8deeb2f30bd04e04645dbbd30dfa9e3 Mon Sep 17 00:00:00 2001 From: George Mamadashvili <georgemamadashvili@gmail.com> Date: Mon, 6 Sep 2021 14:39:10 +0000 Subject: [PATCH 166/214] Limit FSE admin notices to the Themes screen (#34353) * Limit FSE admin notices to the Themes screen * Use Yoda conditions syntax Co-authored-by: Ari Stathopoulos <aristath@gmail.com> Co-authored-by: Ari Stathopoulos <aristath@gmail.com> --- lib/full-site-editing/full-site-editing.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/full-site-editing/full-site-editing.php b/lib/full-site-editing/full-site-editing.php index 0d00847b6b7d2e..d540b840a2dabd 100644 --- a/lib/full-site-editing/full-site-editing.php +++ b/lib/full-site-editing/full-site-editing.php @@ -27,7 +27,7 @@ function gutenberg_supports_block_templates() { * Show a notice when a Full Site Editing theme is used. */ function gutenberg_full_site_editing_notice() { - if ( ! gutenberg_is_fse_theme() ) { + if ( ! gutenberg_is_fse_theme() || 'themes' !== get_current_screen()->base ) { return; } ?> From 867de929f306937bd344f7e56c0e1b231887730a Mon Sep 17 00:00:00 2001 From: Adam Zielinski <adam@adamziel.com> Date: Mon, 6 Sep 2021 16:39:53 +0200 Subject: [PATCH 167/214] Migrate getEntityRecord to thunks (#34576) --- packages/core-data/src/resolvers.js | 33 +++---- packages/core-data/src/test/resolvers.js | 112 +++++++++++++++-------- 2 files changed, 87 insertions(+), 58 deletions(-) diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 7026937858b474..0c5fc493113cd5 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -9,6 +9,8 @@ import { find, includes, get, hasIn, compact, uniq } from 'lodash'; import { addQueryArgs } from '@wordpress/url'; import { controls } from '@wordpress/data'; import { apiFetch } from '@wordpress/data-controls'; +import triggerFetch from '@wordpress/api-fetch'; + /** * Internal dependencies */ @@ -63,16 +65,17 @@ export function* getCurrentUser() { * @param {Object|undefined} query Optional object of query parameters to * include with request. */ -export function* getEntityRecord( kind, name, key = '', query ) { - const entities = yield getKindEntities( kind ); +export const getEntityRecord = ( kind, name, key = '', query ) => async ( { + select, + dispatch, +} ) => { + const entities = await dispatch( getKindEntities( kind ) ); const entity = find( entities, { kind, name } ); if ( ! entity ) { return; } - const lock = yield controls.dispatch( - STORE_NAME, - '__unstableAcquireStoreLock', + const lock = await dispatch.__unstableAcquireStoreLock( STORE_NAME, [ 'entities', 'data', kind, name, key ], { exclusive: false } @@ -110,31 +113,21 @@ export function* getEntityRecord( kind, name, key = '', query ) { // The resolution cache won't consider query as reusable based on the // fields, so it's tested here, prior to initiating the REST request, // and without causing `getEntityRecords` resolution to occur. - const hasRecords = yield controls.select( - STORE_NAME, - 'hasEntityRecords', - kind, - name, - query - ); + const hasRecords = select.hasEntityRecords( kind, name, query ); if ( hasRecords ) { return; } } - const record = yield apiFetch( { path } ); - yield receiveEntityRecords( kind, name, record, query ); + const record = await triggerFetch( { path } ); + dispatch.receiveEntityRecords( kind, name, record, query ); } catch ( error ) { // We need a way to handle and access REST API errors in state // Until then, catching the error ensures the resolver is marked as resolved. } finally { - yield controls.dispatch( - STORE_NAME, - '__unstableReleaseStoreLock', - lock - ); + dispatch.__unstableReleaseStoreLock( lock ); } -} +}; /** * Requests an entity's record from the REST API. diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index 2ff24b126ab8d7..d593b719f838f5 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -3,6 +3,10 @@ */ import { apiFetch } from '@wordpress/data-controls'; +import triggerFetch from '@wordpress/api-fetch'; + +jest.mock( '@wordpress/api-fetch' ); + /** * Internal dependencies */ @@ -32,68 +36,100 @@ describe( 'getEntityRecord', () => { baseURLParams: { context: 'edit' }, }, ]; + beforeEach( async () => { + triggerFetch.mockReset(); + jest.useFakeTimers(); + } ); it( 'yields with requested post type', async () => { - const fulfillment = getEntityRecord( 'root', 'postType', 'post' ); - // Trigger generator - fulfillment.next(); - // Provide entities and acquire lock - expect( fulfillment.next( ENTITIES ).value.type ).toEqual( - '@@data/DISPATCH' - ); - // trigger apiFetch - const { value: apiFetchAction } = fulfillment.next(); - expect( apiFetchAction.request ).toEqual( { + const dispatch = Object.assign( jest.fn(), { + receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), + } ); + // Provide entities + dispatch.mockReturnValueOnce( ENTITIES ); + + // Provide response + triggerFetch.mockImplementation( () => POST_TYPE ); + + await getEntityRecord( 'root', 'postType', 'post' )( { dispatch } ); + + // Fetch request should have been issued + expect( triggerFetch ).toHaveBeenCalledWith( { path: '/wp/v2/types/post?context=edit', } ); - // Provide response and trigger action - const { value: received } = fulfillment.next( POST_TYPE ); - expect( received ).toEqual( - receiveEntityRecords( 'root', 'postType', POST_TYPE ) + + // The record should have been received + expect( dispatch.receiveEntityRecords ).toHaveBeenCalledWith( + 'root', + 'postType', + POST_TYPE, + undefined + ); + + // Locks should have been acquired and released + expect( dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledTimes( + 1 + ); + expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes( + 1 ); - // Release lock - expect( fulfillment.next().value.type ).toEqual( '@@data/DISPATCH' ); } ); it( 'accepts a query that overrides default api path', async () => { const query = { context: 'view', _envelope: '1' }; const queryObj = { include: [ 'post' ], ...query }; - const fulfillment = getEntityRecord( + const select = { + hasEntityRecords: jest.fn( () => {} ), + }; + + const dispatch = Object.assign( jest.fn(), { + receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), + } ); + // Provide entities + dispatch.mockReturnValueOnce( ENTITIES ); + + // Provide response + triggerFetch.mockImplementation( () => POST_TYPE ); + + await getEntityRecord( 'root', 'postType', 'post', query - ); - - // Trigger generator - fulfillment.next(); - - // Provide entities and acquire lock - expect( fulfillment.next( ENTITIES ).value.type ).toEqual( - '@@data/DISPATCH' - ); + )( { dispatch, select } ); // Check resolution cache for an existing entity that fulfills the request with query - const { - value: { args: selectArgs }, - } = fulfillment.next(); - expect( selectArgs ).toEqual( [ 'root', 'postType', queryObj ] ); + expect( select.hasEntityRecords ).toHaveBeenCalledWith( + 'root', + 'postType', + queryObj + ); // Trigger apiFetch, test that the query is present in the url - const { value: apiFetchAction } = fulfillment.next(); - expect( apiFetchAction.request ).toEqual( { + expect( triggerFetch ).toHaveBeenCalledWith( { path: '/wp/v2/types/post?context=view&_envelope=1', } ); - // Receive response - const { value: received } = fulfillment.next( POST_TYPE ); - expect( received ).toEqual( - receiveEntityRecords( 'root', 'postType', POST_TYPE, queryObj ) + // The record should have been received + expect( dispatch.receiveEntityRecords ).toHaveBeenCalledWith( + 'root', + 'postType', + POST_TYPE, + queryObj ); - // Release lock - expect( fulfillment.next().value.type ).toEqual( '@@data/DISPATCH' ); + // Locks should have been acquired and released + expect( dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledTimes( + 1 + ); + expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes( + 1 + ); } ); } ); From 887f0905643212d843fa9e35ab8c814cb6b5be2b Mon Sep 17 00:00:00 2001 From: Adam Zielinski <adam@adamziel.com> Date: Mon, 6 Sep 2021 17:10:11 +0200 Subject: [PATCH 168/214] Fix integration tests (#34578) --- packages/core-data/src/resolvers.js | 39 +++---- packages/core-data/src/test/integration.js | 57 ++++------- packages/core-data/src/test/resolvers.js | 113 +++++++++++++-------- 3 files changed, 116 insertions(+), 93 deletions(-) diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 0c5fc493113cd5..84c3a293cbec4a 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -152,16 +152,24 @@ export const getEditedEntityRecord = ifNotResolved( * @param {string} name Entity name. * @param {Object?} query Query Object. */ -export function* getEntityRecords( kind, name, query = {} ) { - const entities = yield getKindEntities( kind ); + +/** + * Requests the entity's records from the REST API. + * + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {Object?} query Query Object. + */ +export const getEntityRecords = ( kind, name, query = {} ) => async ( { + dispatch, +} ) => { + const entities = await dispatch( getKindEntities( kind ) ); const entity = find( entities, { kind, name } ); if ( ! entity ) { return; } - const lock = yield controls.dispatch( - STORE_NAME, - '__unstableAcquireStoreLock', + const lock = await dispatch.__unstableAcquireStoreLock( STORE_NAME, [ 'entities', 'data', kind, name ], { exclusive: false } @@ -186,7 +194,7 @@ export function* getEntityRecords( kind, name, query = {} ) { ...query, } ); - let records = Object.values( yield apiFetch( { path } ) ); + let records = Object.values( await triggerFetch( { path } ) ); // If we request fields but the result doesn't contain the fields, // explicitely set these fields as "undefined" // that way we consider the query "fullfilled". @@ -202,7 +210,8 @@ export function* getEntityRecords( kind, name, query = {} ) { } ); } - yield receiveEntityRecords( kind, name, records, query ); + dispatch.receiveEntityRecords( kind, name, records, query ); + // When requesting all fields, the list of results can be used to // resolve the `getEntityRecord` selector in addition to `getEntityRecords`. // See https://github.com/WordPress/gutenberg/pull/26575 @@ -212,25 +221,21 @@ export function* getEntityRecords( kind, name, query = {} ) { .filter( ( record ) => record[ key ] ) .map( ( record ) => [ kind, name, record[ key ] ] ); - yield { + dispatch( { type: 'START_RESOLUTIONS', selectorName: 'getEntityRecord', args: resolutionsArgs, - }; - yield { + } ); + dispatch( { type: 'FINISH_RESOLUTIONS', selectorName: 'getEntityRecord', args: resolutionsArgs, - }; + } ); } } finally { - yield controls.dispatch( - STORE_NAME, - '__unstableReleaseStoreLock', - lock - ); + dispatch.__unstableReleaseStoreLock( lock ); } -} +}; getEntityRecords.shouldInvalidate = ( action, kind, name ) => { return ( diff --git a/packages/core-data/src/test/integration.js b/packages/core-data/src/test/integration.js index 6eb1645c506fc7..7411e560436c0d 100644 --- a/packages/core-data/src/test/integration.js +++ b/packages/core-data/src/test/integration.js @@ -19,10 +19,6 @@ jest.mock( '@wordpress/data-controls', () => { apiFetch: jest.fn(), }; } ); -const { apiFetch: actualApiFetch } = jest.requireActual( - '@wordpress/data-controls' -); -import { apiFetch } from '@wordpress/data-controls'; jest.mock( '@wordpress/api-fetch', () => { return { @@ -38,10 +34,13 @@ const runPromise = async ( promise ) => { }; const runPendingPromises = async () => { - jest.runAllTimers(); - const p = new Promise( ( resolve ) => setTimeout( resolve ) ); - jest.runAllTimers(); - await p; + // @TODO: find a better way of exhausting the current event loop queue + for ( let i = 0; i < 100; i++ ) { + jest.runAllTimers(); + const p = new Promise( ( resolve ) => setTimeout( resolve ) ); + jest.runAllTimers(); + await p; + } }; describe( 'receiveEntityRecord', () => { @@ -55,13 +54,11 @@ describe( 'receiveEntityRecord', () => { registry.register( store ); registry.registerStore( 'test/resolution', { actions: { + __unstableAcquireStoreLock: () => ( { type: 'ACQUIRE_LOCK' } ), + __unstableReleaseStoreLock: () => ( { type: 'RELEASE_LOCK' } ), receiveEntityRecords: actions.receiveEntityRecords, - *getEntityRecords( ...args ) { - return yield controls.resolveSelect( - 'test/resolution', - 'getEntityRecords', - ...args - ); + getEntityRecords( ...args ) { + return resolvers.getEntityRecords( ...args ); }, *getEntityRecord( ...args ) { return yield controls.resolveSelect( @@ -82,12 +79,12 @@ describe( 'receiveEntityRecord', () => { getEntityRecord, getEntityRecords: resolvers.getEntityRecords, }, + __experimentalUseThunks: true, } ); return registry; } beforeEach( async () => { - apiFetch.mockReset(); triggerFetch.mockReset(); jest.useFakeTimers(); } ); @@ -96,8 +93,8 @@ describe( 'receiveEntityRecord', () => { const getEntityRecord = jest.fn(); const registry = createTestRegistry( getEntityRecord ); - // Trigger resolution of postType records - apiFetch.mockImplementation( () => ( { + // // Trigger resolution of postType records + triggerFetch.mockImplementation( () => ( { 2: { slug: 'test', id: 2 }, } ) ); await runPromise( @@ -129,7 +126,7 @@ describe( 'receiveEntityRecord', () => { const registry = createTestRegistry( getEntityRecord ); // Trigger resolution of postType records - apiFetch.mockImplementation( () => ( { + triggerFetch.mockImplementation( () => ( { 'test-1': { slug: 'test-1', id: 2 }, } ) ); await runPromise( @@ -165,7 +162,6 @@ describe( 'saveEntityRecord', () => { } beforeEach( async () => { - apiFetch.mockReset(); triggerFetch.mockReset(); jest.useFakeTimers( 'modern' ); } ); @@ -173,34 +169,30 @@ describe( 'saveEntityRecord', () => { it( 'should not trigger any GET requests until POST/PUT is finished.', async () => { const registry = createTestRegistry(); // Fetch post types from the API {{{ - apiFetch.mockImplementation( () => ( { + triggerFetch.mockImplementation( () => ( { 'post-1': { slug: 'post-1' }, } ) ); // Trigger fetch registry.select( 'core' ).getEntityRecords( 'root', 'postType' ); - jest.runAllTimers(); - await Promise.resolve().then( () => jest.runAllTimers() ); - expect( apiFetch ).toBeCalledTimes( 1 ); - expect( apiFetch ).toBeCalledWith( { + await runPendingPromises(); + expect( triggerFetch ).toBeCalledTimes( 1 ); + expect( triggerFetch ).toBeCalledWith( { path: '/wp/v2/types?context=edit', } ); // Select fetched results, there should be no subsequent request - apiFetch.mockReset(); + triggerFetch.mockReset(); const results = registry .select( 'core' ) .getEntityRecords( 'root', 'postType' ); - expect( apiFetch ).toBeCalledTimes( 0 ); - jest.runAllTimers(); - expect( apiFetch ).toBeCalledTimes( 0 ); + expect( triggerFetch ).toBeCalledTimes( 0 ); expect( results ).toHaveLength( 1 ); expect( results[ 0 ].slug ).toBe( 'post-1' ); // }}} Fetch post types from the API // Save changes - apiFetch.mockClear(); - apiFetch.mockImplementation( actualApiFetch ); + triggerFetch.mockClear(); let resolvePromise; triggerFetch.mockImplementation( function () { return new Promise( ( resolve ) => { @@ -214,10 +206,6 @@ describe( 'saveEntityRecord', () => { newField: 'a', } ); - // Wait a few ticks – without rungen we have less control over the flow of things. - // @TODO: A better solution - await runPendingPromises(); - await runPendingPromises(); await runPendingPromises(); // There should ONLY be a single hanging API call (PUT) by this point. @@ -235,7 +223,6 @@ describe( 'saveEntityRecord', () => { } ) ); triggerFetch.mockClear(); - apiFetch.mockClear(); // The PUT is still hanging, let's call a selector now and make sure it won't trigger // any requests diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index d593b719f838f5..1b22b37c75210e 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -2,6 +2,9 @@ * WordPress dependencies */ import { apiFetch } from '@wordpress/data-controls'; +import triggerFetch from '@wordpress/api-fetch'; + +jest.mock( '@wordpress/api-fetch' ); import triggerFetch from '@wordpress/api-fetch'; @@ -153,69 +156,97 @@ describe( 'getEntityRecords', () => { }, ]; - it( 'yields with requested post type', async () => { - const fulfillment = getEntityRecords( 'root', 'postType' ); + beforeEach( async () => { + triggerFetch.mockReset(); + jest.useFakeTimers(); + } ); - // Trigger generator - fulfillment.next(); + it( 'dispatches the requested post type', async () => { + const dispatch = Object.assign( jest.fn(), { + receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), + } ); + // Provide entities + dispatch.mockReturnValueOnce( ENTITIES ); - // Provide entities and acquire lock - fulfillment.next( ENTITIES ); + // Provide response + triggerFetch.mockImplementation( () => POST_TYPES ); - // trigger apiFetch - const { value: apiFetchAction } = fulfillment.next(); + await getEntityRecords( 'root', 'postType' )( { dispatch } ); - expect( apiFetchAction.request ).toEqual( { + // Fetch request should have been issued + expect( triggerFetch ).toHaveBeenCalledWith( { path: '/wp/v2/types?context=edit', } ); - // Provide response and trigger action - const { value: received } = fulfillment.next( POST_TYPES ); - expect( received ).toEqual( - receiveEntityRecords( - 'root', - 'postType', - Object.values( POST_TYPES ), - {} - ) + + // The record should have been received + expect( dispatch.receiveEntityRecords ).toHaveBeenCalledWith( + 'root', + 'postType', + Object.values( POST_TYPES ), + {} ); } ); it( 'Uses state locks', async () => { - const fulfillment = getEntityRecords( 'root', 'postType' ); + const dispatch = Object.assign( jest.fn(), { + receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), + } ); + // Provide entities + dispatch.mockReturnValueOnce( ENTITIES ); - // Repeat the steps from `yields with requested post type` test - fulfillment.next(); - // Provide entities and acquire lock - expect( fulfillment.next( ENTITIES ).value.type ).toEqual( - '@@data/DISPATCH' - ); - fulfillment.next(); - fulfillment.next( POST_TYPES ); + // Provide response + triggerFetch.mockImplementation( () => POST_TYPES ); - // Resolve specific entity records - fulfillment.next(); - fulfillment.next(); + await getEntityRecords( 'root', 'postType' )( { dispatch } ); - // Release lock - expect( fulfillment.next().value.type ).toEqual( '@@data/DISPATCH' ); + // Fetch request should have been issued + expect( triggerFetch ).toHaveBeenCalledWith( { + path: '/wp/v2/types?context=edit', + } ); + + // The record should have been received + expect( + dispatch.__unstableAcquireStoreLock + ).toHaveBeenCalledWith( + 'core', + [ 'entities', 'data', 'root', 'postType' ], + { exclusive: false } + ); + expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes( + 1 + ); } ); it( 'marks specific entity records as resolved', async () => { - const fulfillment = getEntityRecords( 'root', 'postType' ); + const dispatch = Object.assign( jest.fn(), { + receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), + } ); + // Provide entities + dispatch.mockReturnValueOnce( ENTITIES ); - // Repeat the steps from `yields with requested post type` test - fulfillment.next(); - fulfillment.next( ENTITIES ); - fulfillment.next(); - fulfillment.next( POST_TYPES ); + // Provide response + triggerFetch.mockImplementation( () => POST_TYPES ); + + await getEntityRecords( 'root', 'postType' )( { dispatch } ); + + // Fetch request should have been issued + expect( triggerFetch ).toHaveBeenCalledWith( { + path: '/wp/v2/types?context=edit', + } ); - // It should mark the entity record that has an ID as resolved - expect( fulfillment.next().value ).toEqual( { + // The record should have been received + expect( dispatch ).toHaveBeenCalledWith( { type: 'START_RESOLUTIONS', selectorName: 'getEntityRecord', args: [ [ ENTITIES[ 1 ].kind, ENTITIES[ 1 ].name, 2 ] ], } ); - expect( fulfillment.next().value ).toEqual( { + expect( dispatch ).toHaveBeenCalledWith( { type: 'FINISH_RESOLUTIONS', selectorName: 'getEntityRecord', args: [ [ ENTITIES[ 1 ].kind, ENTITIES[ 1 ].name, 2 ] ], From 5f584c8a7f3594b7f2535545da712ed6a8c36462 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras <ntsekouras@outlook.com> Date: Mon, 6 Sep 2021 20:50:02 +0300 Subject: [PATCH 169/214] Fix linting errors (#34596) --- packages/core-data/src/test/resolvers.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index 1b22b37c75210e..7f517291bb2038 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -6,10 +6,6 @@ import triggerFetch from '@wordpress/api-fetch'; jest.mock( '@wordpress/api-fetch' ); -import triggerFetch from '@wordpress/api-fetch'; - -jest.mock( '@wordpress/api-fetch' ); - /** * Internal dependencies */ @@ -22,7 +18,6 @@ import { getCurrentUser, } from '../resolvers'; import { - receiveEntityRecords, receiveEmbedPreview, receiveUserPermission, receiveAutosaves, From bd8da4087a63ca9f8723b59a81d244b89c57a6bf Mon Sep 17 00:00:00 2001 From: Nik Tsekouras <ntsekouras@outlook.com> Date: Mon, 6 Sep 2021 23:46:27 +0300 Subject: [PATCH 170/214] [Components - ToggleGroupControl]: Update stories to use knobs (#34497) * [Components - ToggleGroupControl]: Update stories to use knobs * adress feedback * add knobs for changing label in every option * update knobs labels --- .../src/toggle-group-control/stories/index.js | 98 +++++++++++-------- .../src/toggle-group-control/types.ts | 2 +- 2 files changed, 58 insertions(+), 42 deletions(-) diff --git a/packages/components/src/toggle-group-control/stories/index.js b/packages/components/src/toggle-group-control/stories/index.js index 2944afb9486e20..32145f3c086b8d 100644 --- a/packages/components/src/toggle-group-control/stories/index.js +++ b/packages/components/src/toggle-group-control/stories/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { boolean, text } from '@storybook/addon-knobs'; + /** * WordPress dependencies */ @@ -6,7 +11,6 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import { __experimentalSpacer as Spacer } from '../../'; import { ToggleGroupControl, ToggleGroupControlOption } from '../index'; import { View } from '../../view'; @@ -16,52 +20,64 @@ export default { }; const aligns = [ 'Left', 'Center', 'Right', 'Justify' ]; -const alignOptions = aligns.map( ( key ) => ( - <ToggleGroupControlOption key={ key } value={ key } label={ key } /> -) ); +const KNOBS_GROUPS = { + ToggleGroupControl: 'ToggleGroupControl', + ToggleGroupControlOption: 'ToggleGroupControlOption', +}; export const _default = () => { const [ alignState, setAlignState ] = useState( aligns[ 0 ] ); - const label = 'Toggle Group Control'; + const label = text( + `${ KNOBS_GROUPS.ToggleGroupControl }: label`, + 'Toggle Group Control', + KNOBS_GROUPS.ToggleGroupControl + ); + const hideLabelFromVision = boolean( + `${ KNOBS_GROUPS.ToggleGroupControl }: hideLabelFromVision`, + false, + KNOBS_GROUPS.ToggleGroupControl + ); + const isBlock = boolean( + `${ KNOBS_GROUPS.ToggleGroupControl }: isBlock (render as a css block element)`, + false, + KNOBS_GROUPS.ToggleGroupControl + ); + const help = text( + `${ KNOBS_GROUPS.ToggleGroupControl }: help`, + undefined, + KNOBS_GROUPS.ToggleGroupControl + ); + const isAdaptiveWidth = boolean( + `${ KNOBS_GROUPS.ToggleGroupControl }: isAdaptiveWidth`, + false, + KNOBS_GROUPS.ToggleGroupControl + ); + + const alignOptions = aligns.map( ( key, index ) => ( + <ToggleGroupControlOption + key={ key } + value={ key } + label={ text( + `${ KNOBS_GROUPS.ToggleGroupControlOption }: label`, + key, + `${ KNOBS_GROUPS.ToggleGroupControlOption }-${ index + 1 }` + ) } + /> + ) ); return ( <View> - <Spacer> - <ToggleGroupControl - isBlock - onChange={ setAlignState } - value={ alignState } - label={ label } - > - { alignOptions } - </ToggleGroupControl> - </Spacer> - <Spacer> - <ToggleGroupControl label={ label } value="horizontal"> - <ToggleGroupControlOption - value="horizontal" - label="Horizontal" - /> - <ToggleGroupControlOption - value="vertical" - label="Vertical" - /> - </ToggleGroupControl> - </Spacer> - <Spacer> - <ToggleGroupControl - isAdaptiveWidth - label={ label } - value="long" - hideLabelFromVision={ true } - > - <ToggleGroupControlOption value="short" label="Short" /> - <ToggleGroupControlOption - value="long" - label="Looooooooooooong" - /> - </ToggleGroupControl> - </Spacer> + <ToggleGroupControl + onChange={ setAlignState } + value={ alignState } + label={ label } + hideLabelFromVision={ hideLabelFromVision } + help={ help } + isBlock={ isBlock } + isAdaptiveWidth={ isAdaptiveWidth } + > + { alignOptions } + </ToggleGroupControl> </View> ); }; diff --git a/packages/components/src/toggle-group-control/types.ts b/packages/components/src/toggle-group-control/types.ts index ad08436e8da7fc..81ee891b285af8 100644 --- a/packages/components/src/toggle-group-control/types.ts +++ b/packages/components/src/toggle-group-control/types.ts @@ -29,7 +29,7 @@ export type ToggleGroupControlProps = Omit< * * @default false */ - hideLabelFromVision: boolean; + hideLabelFromVision?: boolean; /** * Determines if segments should be rendered with equal widths. * From d1427be909eee11ff58adabef9b9cdfd436cb766 Mon Sep 17 00:00:00 2001 From: Mitchell Austin <mr.fye@oneandthesame.net> Date: Mon, 6 Sep 2021 21:21:04 -0700 Subject: [PATCH 171/214] =?UTF-8?q?Prevent=20focus=20trap=20in=20Legacy=20?= =?UTF-8?q?Widget=20block=E2=80=99s=20preview=20iframe=20(#33614)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/widgets/src/blocks/legacy-widget/edit/preview.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/widgets/src/blocks/legacy-widget/edit/preview.js b/packages/widgets/src/blocks/legacy-widget/edit/preview.js index f8407bfb82652c..3d731672e87435 100644 --- a/packages/widgets/src/blocks/legacy-widget/edit/preview.js +++ b/packages/widgets/src/blocks/legacy-widget/edit/preview.js @@ -123,6 +123,7 @@ export default function Preview( { idBase, instance, isVisible } ) { <iframe ref={ ref } className="wp-block-legacy-widget__edit-preview-iframe" + tabIndex="-1" title={ __( 'Legacy Widget Preview' ) } srcDoc={ srcDoc } onLoad={ ( event ) => { From f2de29830423d8c0890add9889ee07d6bf7163f1 Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Tue, 7 Sep 2021 14:28:07 +1000 Subject: [PATCH 172/214] Add 'Widget Group' block to widgets screens (#34484) * Take all of the work on Widget Box that @getdave did * Rename 'core/widget-box' to 'core/widget-group' * Set up flow inspired by Legacy Widget block * Default title to the first block's label * Add styling * Add space to comment Co-authored-by: Kai Hao <kevin830726@gmail.com> * Fix custom title not appearing in frontend * Use solid line to match reusable block styling * Prevent Widget Group from appearing in its own list of valid transforms * Change title of Widget Group form to 'Widget Group' * Use useSelect properly * Remove unnecessary eslint-ignore * Fix Widget Group in Customizer * Update Widget Group block description * Fix misspelling of 'argument' * Remove unused file * Default TextControl value to '' * Force appender to always have light styling Co-authored-by: Dave Smith <getdavemail@gmail.com> Co-authored-by: Kai Hao <kevin830726@gmail.com> --- .../block-api/block-transforms.md | 2 +- lib/blocks.php | 2 + packages/blocks/CHANGELOG.md | 4 + packages/blocks/src/api/factory.js | 3 +- packages/blocks/src/api/test/factory.js | 14 ++-- packages/customize-widgets/src/index.js | 2 + packages/edit-widgets/src/index.js | 4 + .../src/blocks/widget-group/block.json | 18 ++++ .../widgets/src/blocks/widget-group/edit.js | 84 +++++++++++++++++++ .../src/blocks/widget-group/editor.scss | 46 ++++++++++ .../widgets/src/blocks/widget-group/index.js | 76 +++++++++++++++++ .../widgets/src/blocks/widget-group/index.php | 84 +++++++++++++++++++ .../widgets/src/blocks/widget-group/save.js | 17 ++++ packages/widgets/src/index.js | 9 ++ packages/widgets/src/style.scss | 1 + 15 files changed, 357 insertions(+), 9 deletions(-) create mode 100644 packages/widgets/src/blocks/widget-group/block.json create mode 100644 packages/widgets/src/blocks/widget-group/edit.js create mode 100644 packages/widgets/src/blocks/widget-group/editor.scss create mode 100644 packages/widgets/src/blocks/widget-group/index.js create mode 100644 packages/widgets/src/blocks/widget-group/index.php create mode 100644 packages/widgets/src/blocks/widget-group/save.js diff --git a/docs/reference-guides/block-api/block-transforms.md b/docs/reference-guides/block-api/block-transforms.md index 1078608345e526..d1de0e98fa12ac 100644 --- a/docs/reference-guides/block-api/block-transforms.md +++ b/docs/reference-guides/block-api/block-transforms.md @@ -42,7 +42,7 @@ A transformation of type `block` is an object that takes the following parameter - **type** _(string)_: the value `block`. - **blocks** _(array)_: a list of known block types. It also accepts the wildcard value (`"*"`), meaning that the transform is available to _all_ block types (eg: all blocks can transform into `core/group`). - **transform** _(function)_: a callback that receives the attributes and inner blocks of the block being processed. It should return a block object or an array of block objects. -- **isMatch** _(function, optional)_: a callback that receives the block attributes and should return a boolean. Returning `false` from this function will prevent the transform from being available and displayed as an option to the user. +- **isMatch** _(function, optional)_: a callback that receives the block attributes as the first argument and the block object as the second argument and should return a boolean. Returning `false` from this function will prevent the transform from being available and displayed as an option to the user. - **isMultiBlock** _(boolean, optional)_: whether the transformation can be applied when multiple blocks are selected. If true, the `transform` function's first parameter will be an array containing each selected block's attributes, and the second an array of each selected block's inner blocks. False by default. - **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. diff --git a/lib/blocks.php b/lib/blocks.php index cc3f4c8662254e..77dcaae989b556 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -106,9 +106,11 @@ function gutenberg_reregister_core_block_types() { __DIR__ . '/../build/widgets/blocks/' => array( 'block_folders' => array( 'legacy-widget', + 'widget-group', ), 'block_names' => array( 'legacy-widget.php' => 'core/legacy-widget', + 'widget-group.php' => 'core/widget-group', ), ), ); diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index b51c240a069ea1..486282484503c1 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -6,6 +6,10 @@ - Register a block even when an invalid value provided for the icon setting ([#34350](https://github.com/WordPress/gutenberg/pull/34350)). +### New API + +- The `isMatch` callback on block transforms now receives the block object (or block objects if `isMulti` is `true`) as its second argument. + ## 11.0.0 (2021-07-29) ### Breaking Change diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index 5b1239c46fc7cf..3013a3d9f79e5c 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -224,7 +224,8 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { const attributes = transform.isMultiBlock ? blocks.map( ( block ) => block.attributes ) : sourceBlock.attributes; - if ( ! transform.isMatch( attributes ) ) { + const block = transform.isMultiBlock ? blocks : sourceBlock; + if ( ! transform.isMatch( attributes, block ) ) { return false; } } diff --git a/packages/blocks/src/api/test/factory.js b/packages/blocks/src/api/test/factory.js index fc38d753e997b6..3b4d8671f6e1ba 100644 --- a/packages/blocks/src/api/test/factory.js +++ b/packages/blocks/src/api/test/factory.js @@ -979,7 +979,7 @@ describe( 'block factory', () => { expect( availableBlocks ).toEqual( [] ); } ); - it( 'for a non multiblock transform, the isMatch function receives the source block’s attributes object as its first argument', () => { + it( 'for a non multiblock transform, the isMatch function receives the source block’s attributes object and the block object as its arguments', () => { const isMatch = jest.fn(); registerBlockType( 'core/updated-text-block', { @@ -1010,10 +1010,10 @@ describe( 'block factory', () => { getPossibleBlockTransformations( [ block ] ); - expect( isMatch ).toHaveBeenCalledWith( { value: 'ribs' } ); + expect( isMatch ).toHaveBeenCalledWith( { value: 'ribs' }, block ); } ); - it( 'for a multiblock transform, the isMatch function receives an array containing every source block’s attributes as its first argument', () => { + it( 'for a multiblock transform, the isMatch function receives an array containing every source block’s attributes and an array of source blocks as its arguments', () => { const isMatch = jest.fn(); registerBlockType( 'core/updated-text-block', { @@ -1049,10 +1049,10 @@ describe( 'block factory', () => { getPossibleBlockTransformations( [ meatBlock, cheeseBlock ] ); - expect( isMatch ).toHaveBeenCalledWith( [ - { value: 'ribs' }, - { value: 'halloumi' }, - ] ); + expect( isMatch ).toHaveBeenCalledWith( + [ { value: 'ribs' }, { value: 'halloumi' } ], + [ meatBlock, cheeseBlock ] + ); } ); describe( 'wildcard block transforms', () => { diff --git a/packages/customize-widgets/src/index.js b/packages/customize-widgets/src/index.js index 8751caab3a8115..ef93ba1264c8b7 100644 --- a/packages/customize-widgets/src/index.js +++ b/packages/customize-widgets/src/index.js @@ -10,6 +10,7 @@ import { import { registerLegacyWidgetBlock, registerLegacyWidgetVariations, + registerWidgetGroupBlock, } from '@wordpress/widgets'; import { setFreeformContentHandlerName } from '@wordpress/blocks'; import { dispatch } from '@wordpress/data'; @@ -56,6 +57,7 @@ export function initialize( editorName, blockEditorSettings ) { } ); } registerLegacyWidgetVariations( blockEditorSettings ); + registerWidgetGroupBlock(); // As we are unregistering `core/freeform` to avoid the Classic block, we must // replace it with something as the default freeform content handler. Failure to diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index 15961a863047e6..c45bb294ab46fa 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -16,6 +16,7 @@ import { __experimentalFetchLinkSuggestions as fetchLinkSuggestions } from '@wor import { registerLegacyWidgetBlock, registerLegacyWidgetVariations, + registerWidgetGroupBlock, } from '@wordpress/widgets'; import { dispatch } from '@wordpress/data'; import { store as interfaceStore } from '@wordpress/interface'; @@ -26,6 +27,7 @@ import { store as interfaceStore } from '@wordpress/interface'; import './store'; import './filters'; import * as widgetArea from './blocks/widget-area'; + import Layout from './components/layout'; import { ALLOW_REUSABLE_BLOCKS, @@ -89,6 +91,8 @@ export function initialize( id, settings ) { } registerLegacyWidgetVariations( settings ); registerBlock( widgetArea ); + registerWidgetGroupBlock(); + settings.__experimentalFetchLinkSuggestions = ( search, searchOptions ) => fetchLinkSuggestions( search, searchOptions, settings ); diff --git a/packages/widgets/src/blocks/widget-group/block.json b/packages/widgets/src/blocks/widget-group/block.json new file mode 100644 index 00000000000000..ec48d90eda5ca7 --- /dev/null +++ b/packages/widgets/src/blocks/widget-group/block.json @@ -0,0 +1,18 @@ +{ + "apiVersion": 2, + "name": "core/widget-group", + "category": "widgets", + "attributes": { + "title": { + "type": "string" + } + }, + "supports": { + "html": false, + "inserter": true, + "customClassName": true, + "reusable": false + }, + "editorStyle": "wp-block-widget-group-editor", + "style": "wp-block-widget-group" +} diff --git a/packages/widgets/src/blocks/widget-group/edit.js b/packages/widgets/src/blocks/widget-group/edit.js new file mode 100644 index 00000000000000..663946364904de --- /dev/null +++ b/packages/widgets/src/blocks/widget-group/edit.js @@ -0,0 +1,84 @@ +/** + * WordPress dependencies + */ +import { + useBlockProps, + BlockIcon, + ButtonBlockAppender, + InnerBlocks, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { Placeholder, TextControl } from '@wordpress/components'; +import { group as groupIcon } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { getBlockType } from '@wordpress/blocks'; + +export default function Edit( props ) { + const { clientId, isSelected } = props; + const { innerBlocks } = useSelect( ( select ) => + select( blockEditorStore ).getBlock( clientId ) + ); + + let content; + if ( innerBlocks.length === 0 ) { + content = <PlaceholderContent { ...props } />; + } else if ( isSelected ) { + content = <EditFormContent { ...props } innerBlocks={ innerBlocks } />; + } else { + content = <PreviewContent { ...props } innerBlocks={ innerBlocks } />; + } + + return ( + <div { ...useBlockProps( { className: 'widget' } ) }>{ content }</div> + ); +} + +function PlaceholderContent( { clientId } ) { + return ( + <> + <Placeholder + className="wp-block-widget-group__placeholder" + icon={ <BlockIcon icon={ groupIcon } /> } + label={ __( 'Widget Group' ) } + > + <ButtonBlockAppender rootClientId={ clientId } /> + </Placeholder> + <InnerBlocks renderAppender={ false } /> + </> + ); +} + +function EditFormContent( { attributes, setAttributes, innerBlocks } ) { + return ( + <div className="wp-block-widget-group__edit-form"> + <h2 className="wp-block-widget-group__edit-form-title"> + { __( 'Widget Group' ) } + </h2> + <TextControl + label={ __( 'Title' ) } + placeholder={ getDefaultTitle( innerBlocks ) } + value={ attributes.title ?? '' } + onChange={ ( title ) => setAttributes( { title } ) } + /> + </div> + ); +} + +function PreviewContent( { attributes, innerBlocks } ) { + return ( + <> + <h2 className="widget-title"> + { attributes.title || getDefaultTitle( innerBlocks ) } + </h2> + <InnerBlocks /> + </> + ); +} + +function getDefaultTitle( innerBlocks ) { + if ( innerBlocks.length === 0 ) { + return null; + } + return getBlockType( innerBlocks[ 0 ].name ).title; +} diff --git a/packages/widgets/src/blocks/widget-group/editor.scss b/packages/widgets/src/blocks/widget-group/editor.scss new file mode 100644 index 00000000000000..df7a459e383682 --- /dev/null +++ b/packages/widgets/src/blocks/widget-group/editor.scss @@ -0,0 +1,46 @@ +.wp-block-widget-group { + &.has-child-selected::after { + border-radius: $radius-block-ui; + border: 1px solid var(--wp-admin-theme-color); + bottom: 0; + content: ""; + left: 0; + position: absolute; + right: 0; + top: 0; + } + + .widget-title { + font-family: $default-font; + font-size: 18px; + font-weight: 600; + } +} + +.wp-block-widget-group__placeholder { + .block-editor-inserter { + width: 100%; + } +} + +// Force the appender to always have "light mode" styling as it appears in a +// light colored placeholder. +.is-dark-theme .wp-block-widget-group__placeholder .block-editor-button-block-appender { + box-shadow: inset 0 0 0 $border-width $gray-900; + color: $gray-900; +} + +.wp-block-widget-group__edit-form { + background: $white; + border-radius: $radius-block-ui; + border: 1px solid $gray-900; + padding: $grid-unit-15 - 1px; // Subtract the border width. + + .wp-block-widget-group__edit-form-title { + color: $black; + font-family: $default-font; + font-size: 14px; + font-weight: 600; + margin: 0 0 $grid-unit-15 0; + } +} diff --git a/packages/widgets/src/blocks/widget-group/index.js b/packages/widgets/src/blocks/widget-group/index.js new file mode 100644 index 00000000000000..012a4a0954bb24 --- /dev/null +++ b/packages/widgets/src/blocks/widget-group/index.js @@ -0,0 +1,76 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { createBlock } from '@wordpress/blocks'; +import { group as icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import edit from './edit'; +import save from './save'; +const { name } = metadata; +export { metadata, name }; + +export const settings = { + title: __( 'Widget Group' ), + description: __( + 'Create a classic widget layout with a title that’s styled by your theme for your widget areas.' + ), + icon, + __experimentalLabel: ( { name: label } ) => label, + edit, + save, + transforms: { + from: [ + { + type: 'block', + isMultiBlock: true, + blocks: [ '*' ], + isMatch( attributes, blocks ) { + // Avoid transforming existing `widget-group` blocks. + return ! blocks.some( + ( block ) => block.name === 'core/widget-group' + ); + }, + __experimentalConvert( blocks ) { + // Put the selected blocks inside the new Widget Group's innerBlocks. + let innerBlocks = [ + ...blocks.map( ( block ) => { + return createBlock( + block.name, + block.attributes, + block.innerBlocks + ); + } ), + ]; + + // If the first block is a heading then assume this is intended + // to be the Widget's "title". + const firstHeadingBlock = + innerBlocks[ 0 ].name === 'core/heading' + ? innerBlocks[ 0 ] + : null; + + // Remove the first heading block as we're copying + // it's content into the Widget Group's title attribute. + innerBlocks = innerBlocks.filter( + ( block ) => block !== firstHeadingBlock + ); + + return createBlock( + 'core/widget-group', + { + ...( firstHeadingBlock && { + title: firstHeadingBlock.attributes.content, + } ), + }, + innerBlocks + ); + }, + }, + ], + }, +}; diff --git a/packages/widgets/src/blocks/widget-group/index.php b/packages/widgets/src/blocks/widget-group/index.php new file mode 100644 index 00000000000000..b225b08387385d --- /dev/null +++ b/packages/widgets/src/blocks/widget-group/index.php @@ -0,0 +1,84 @@ +<?php +/** + * Server-side rendering of the `core/widget-group` block. + * + * @package WordPress + */ + +/** + * Renders the 'core/widget-group' block. + * + * @param array $attributes The block attributes. + * @param string $content The block content. + * @param WP_Block $block The block. + * + * @return string Rendered block. + */ +function render_block_core_widget_group( $attributes, $content, $block ) { + global $wp_registered_sidebars, $_sidebar_being_rendered; + + if ( isset( $wp_registered_sidebars[ $_sidebar_being_rendered ] ) ) { + $before_title = $wp_registered_sidebars[ $_sidebar_being_rendered ]['before_title']; + $after_title = $wp_registered_sidebars[ $_sidebar_being_rendered ]['after_title']; + } else { + $before_title = '<h2 class="widget-title">'; + $after_title = '</h2>'; + } + + $html = ''; + + if ( ! empty( $attributes['title'] ) ) { + $title = $attributes['title']; + } elseif ( ! empty( $block->inner_blocks ) ) { + $title = $block->inner_blocks[0]->block_type->title; + } + + if ( isset( $title ) ) { + $html .= $before_title . $title . $after_title; + } + + $html .= '<div class="wp-widget-group__inner-blocks">'; + foreach ( $block->inner_blocks as $inner_block ) { + $html .= $inner_block->render(); + } + $html .= '</div>'; + + return $html; +} + +/** + * Registers the 'core/widget-group' block. + */ +function register_block_core_widget_group() { + register_block_type_from_metadata( + __DIR__ . '/widget-group', + array( + 'render_callback' => 'render_block_core_widget_group', + ) + ); +} + +add_action( 'init', 'register_block_core_widget_group' ); + +/** + * Make a note of the sidebar being rendered before WordPress starts rendering + * it. This lets us get to the current sidebar in + * render_block_core_widget_group(). + * + * @param int|string $index Index, name, or ID of the dynamic sidebar. + */ +function note_sidebar_being_rendered( $index ) { + global $_sidebar_being_rendered; + $_sidebar_being_rendered = $index; +} +add_action( 'dynamic_sidebar_before', 'note_sidebar_being_rendered' ); + +/** + * Clear whatever we set in note_sidebar_being_rendered() after WordPress + * finishes rendering a sidebar. + */ +function discard_sidebar_being_rendered() { + global $_sidebar_being_rendered; + unset( $_sidebar_being_rendered ); +} +add_action( 'dynamic_sidebar_after', 'discard_sidebar_being_rendered' ); diff --git a/packages/widgets/src/blocks/widget-group/save.js b/packages/widgets/src/blocks/widget-group/save.js new file mode 100644 index 00000000000000..203d063c27ca05 --- /dev/null +++ b/packages/widgets/src/blocks/widget-group/save.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { InnerBlocks, RichText } from '@wordpress/block-editor'; + +export default function save( { attributes } ) { + return ( + <> + <RichText.Content + tagName="h2" + className="widget-title" + value={ attributes.title } + /> + <InnerBlocks.Content /> + </> + ); +} diff --git a/packages/widgets/src/index.js b/packages/widgets/src/index.js index c02afc7a3f57cf..6c88d65556d8b0 100644 --- a/packages/widgets/src/index.js +++ b/packages/widgets/src/index.js @@ -7,6 +7,7 @@ import { registerBlockType } from '@wordpress/blocks'; * Internal dependencies */ import * as legacyWidget from './blocks/legacy-widget'; +import * as widgetGroup from './blocks/widget-group'; export * from './components'; export * from './utils'; @@ -24,4 +25,12 @@ export function registerLegacyWidgetBlock() { registerBlockType( { name, ...metadata }, settings ); } +/** + * Registers the Widget Group block. + */ +export function registerWidgetGroupBlock() { + const { metadata, settings, name } = widgetGroup; + registerBlockType( { name, ...metadata }, settings ); +} + export { default as registerLegacyWidgetVariations } from './register-legacy-widget-variations'; diff --git a/packages/widgets/src/style.scss b/packages/widgets/src/style.scss index 4ba09348938568..72ee31dd3b859a 100644 --- a/packages/widgets/src/style.scss +++ b/packages/widgets/src/style.scss @@ -1 +1,2 @@ @import "./blocks/legacy-widget/editor.scss"; +@import "./blocks/widget-group/editor.scss"; From 45fe61e3c9b59b94218f8e483cbe64b34dfb2ed6 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Tue, 7 Sep 2021 13:02:37 +0800 Subject: [PATCH 173/214] Fix undo/redo 'trap' in navigation link block (#34565) * Mark side effecty code as not persistent change * Add comment --- .../block-library/src/navigation-link/edit.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index fa93ace9b5de53..fce0a339b2e252 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -291,7 +291,10 @@ export default function NavigationLinkEdit( { }; const { showSubmenuIcon } = context; const { saveEntityRecord } = useDispatch( coreStore ); - const { insertBlock } = useDispatch( blockEditorStore ); + const { + insertBlock, + __unstableMarkNextChangeAsNotPersistent, + } = useDispatch( blockEditorStore ); const [ isLinkOpen, setIsLinkOpen ] = useState( false ); const listItemRef = useRef( null ); const isDraggingWithin = useIsDraggingWithin( listItemRef ); @@ -354,8 +357,14 @@ export default function NavigationLinkEdit( { [ clientId ] ); - // Store the colors from context as attributes for rendering - useEffect( () => setAttributes( { isTopLevelLink } ), [ isTopLevelLink ] ); + useEffect( () => { + // This side-effect should not create an undo level as those should + // only be created via user interactions. Mark this change as + // not persistent to avoid undo level creation. + // See https://github.com/WordPress/gutenberg/issues/34564. + __unstableMarkNextChangeAsNotPersistent(); + setAttributes( { isTopLevelLink } ); + }, [ isTopLevelLink ] ); /** * Insert a link block when submenu is added. From d9bebc93d2f8856d504894986b2a88beb85840fc Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed <amustaque97@gmail.com> Date: Tue, 7 Sep 2021 11:06:02 +0530 Subject: [PATCH 174/214] Prettier - update README.md file with the correct syntax (#34600) Fixes #34555. This commit is to update readme file. --- packages/prettier-config/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/prettier-config/README.md b/packages/prettier-config/README.md index 901b6ca61d20f0..68730d2d4d543e 100644 --- a/packages/prettier-config/README.md +++ b/packages/prettier-config/README.md @@ -23,7 +23,7 @@ Add this to your `package.json` file: Alternatively, add this to `.prettierrc` file: ``` -extends @wordpress/prettier-config +extends: ['@wordpress/prettier-config'] ``` <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From 5c5fd35f22d4c574ceb7b4a2ec3b63a2dde8c4d5 Mon Sep 17 00:00:00 2001 From: Glen Davies <glendaviesnz@users.noreply.github.com> Date: Tue, 7 Sep 2021 17:39:31 +1200 Subject: [PATCH 175/214] Add docblock comments to the new gallery hooks (#34562) Co-authored-by: Glen Davies <glen.davies@a8c.com> --- packages/block-library/src/gallery/use-get-media.js | 8 ++++++++ .../block-library/src/gallery/use-get-new-images.js | 10 ++++++++++ packages/block-library/src/gallery/use-image-sizes.js | 10 ++++++++++ .../src/gallery/use-short-code-transform.js | 8 ++++++++ 4 files changed, 36 insertions(+) diff --git a/packages/block-library/src/gallery/use-get-media.js b/packages/block-library/src/gallery/use-get-media.js index 1bf33c767a4c64..2d70119e4aa229 100644 --- a/packages/block-library/src/gallery/use-get-media.js +++ b/packages/block-library/src/gallery/use-get-media.js @@ -5,6 +5,14 @@ import { useState } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; +/** + * Retrieves the extended media info for each gallery image from the store. This is used to + * determine which image size options are available for the current gallery. + * + * @param {Array} innerBlockImages An array of the innerBlock images currently in the gallery. + * + * @return {Array} An array of media info options for each gallery image. + */ export default function useGetMedia( innerBlockImages ) { const [ currentImageMedia, setCurrentImageMedia ] = useState( [] ); diff --git a/packages/block-library/src/gallery/use-get-new-images.js b/packages/block-library/src/gallery/use-get-new-images.js index 67578a5cb9941b..056cf1d2ff6ba1 100644 --- a/packages/block-library/src/gallery/use-get-new-images.js +++ b/packages/block-library/src/gallery/use-get-new-images.js @@ -3,6 +3,16 @@ */ import { useMemo, useState } from '@wordpress/element'; +/** + * Keeps track of images already in the gallery to allow new innerBlocks to be identified. This + * is required so default gallery attributes can be applied without overwriting any custom + * attributes applied to existing images. + * + * @param {Array} images Basic image block data taken from current gallery innerBlock + * @param {Array} imageData The related image data for each of the current gallery images. + * + * @return {Array} An array of any new images that have been added to the gallery. + */ export default function useGetNewImages( images, imageData ) { const [ currentImages, setCurrentImages ] = useState( [] ); diff --git a/packages/block-library/src/gallery/use-image-sizes.js b/packages/block-library/src/gallery/use-image-sizes.js index 5b406adab4cc9a..877bacb67dd137 100644 --- a/packages/block-library/src/gallery/use-image-sizes.js +++ b/packages/block-library/src/gallery/use-image-sizes.js @@ -8,6 +8,16 @@ import { get, some } from 'lodash'; */ import { useMemo } from '@wordpress/element'; +/** + * Calculates the image sizes that are avaible for the current gallery images in order to + * populate the 'Image size' selector. + * + * @param {Array} images Basic image block data taken from current gallery innerBlock + * @param {boolean} isSelected Is the block currently selected in the editor. + * @param {Function} getSettings Block editor store selector. + * + * @return {Array} An array of image size options. + */ export default function useImageSizes( images, isSelected, getSettings ) { return useMemo( () => getImageSizing(), [ images, isSelected ] ); diff --git a/packages/block-library/src/gallery/use-short-code-transform.js b/packages/block-library/src/gallery/use-short-code-transform.js index 3843df9d5a6427..bc251119104083 100644 --- a/packages/block-library/src/gallery/use-short-code-transform.js +++ b/packages/block-library/src/gallery/use-short-code-transform.js @@ -9,6 +9,14 @@ import { every } from 'lodash'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; +/** + * Shortcode transforms don't currently have a tranform method and so can't use a selector to + * retrieve the data for each image being transformer, so this selector handle this post transformation. + * + * @param {Array} shortCodeTransforms An array of image data passed from the shortcode transform. + * + * @return {Array} An array of extended image data objects for each of the shortcode transform images. + */ export default function useShortCodeTransform( shortCodeTransforms ) { const newImageData = useSelect( ( select ) => { From a1151b674e4235e5aa30c551b13fbe81f0523993 Mon Sep 17 00:00:00 2001 From: Adam Zielinski <adam@adamziel.com> Date: Tue, 7 Sep 2021 08:22:54 +0200 Subject: [PATCH 176/214] Use resolveSelect instead of select in saveEntityRecord (#34584) --- packages/core-data/src/actions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 0cc8bdaa7aefeb..d0d7cd12a75766 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -348,7 +348,7 @@ export const saveEntityRecord = ( name, record, { isAutosave = false, __unstableFetch = triggerFetch } = {} -) => async ( { select, dispatch } ) => { +) => async ( { select, resolveSelect, dispatch } ) => { const entities = await dispatch( getKindEntities( kind ) ); const entity = find( entities, { kind, name } ); if ( ! entity ) { @@ -410,7 +410,7 @@ export const saveEntityRecord = ( // so the client just sends and receives objects. const currentUser = select.getCurrentUser(); const currentUserId = currentUser ? currentUser.id : undefined; - const autosavePost = select.getAutosave( + const autosavePost = resolveSelect.getAutosave( persistedRecord.type, persistedRecord.id, currentUserId From 5341b5a1f5292fedea2501b9a7b25fbca3ff3c5d Mon Sep 17 00:00:00 2001 From: Adam Zielinski <adam@adamziel.com> Date: Tue, 7 Sep 2021 08:47:20 +0200 Subject: [PATCH 177/214] Migrate getAutosaves to thunks (#34581) --- packages/core-data/src/resolvers.js | 31 ++++++-------- packages/core-data/src/test/resolvers.js | 51 ++++++++++++------------ 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 84c3a293cbec4a..0e6ec93bd83dd6 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -28,7 +28,6 @@ import { receiveThemeSupports, receiveEmbedPreview, receiveUserPermission, - receiveAutosaves, } from './actions'; import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; import { ifNotResolved, getNormalizedCommaSeparable } from './utils'; @@ -365,20 +364,19 @@ export function* canUserEditEntityRecord( kind, name, recordId ) { * @param {string} postType The type of the parent post. * @param {number} postId The id of the parent post. */ -export function* getAutosaves( postType, postId ) { - const { rest_base: restBase } = yield controls.resolveSelect( - STORE_NAME, - 'getPostType', - postType - ); - const autosaves = yield apiFetch( { +export const getAutosaves = ( postType, postId ) => async ( { + dispatch, + resolveSelect, +} ) => { + const { rest_base: restBase } = await resolveSelect.getPostType( postType ); + const autosaves = await triggerFetch( { path: `/wp/v2/${ restBase }/${ postId }/autosaves?context=edit`, } ); if ( autosaves && autosaves.length ) { - yield receiveAutosaves( postId, autosaves ); + dispatch.receiveAutosaves( postId, autosaves ); } -} +}; /** * Request autosave data from the REST API. @@ -389,14 +387,11 @@ export function* getAutosaves( postType, postId ) { * @param {string} postType The type of the parent post. * @param {number} postId The id of the parent post. */ -export function* getAutosave( postType, postId ) { - yield controls.resolveSelect( - STORE_NAME, - 'getAutosaves', - postType, - postId - ); -} +export const getAutosave = ( postType, postId ) => async ( { + resolveSelect, +} ) => { + await resolveSelect.getAutosaves( postType, postId ); +}; /** * Retrieve the frontend template used for a given link. diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index 7f517291bb2038..91d28f6ab5ea42 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -20,7 +20,6 @@ import { import { receiveEmbedPreview, receiveUserPermission, - receiveAutosaves, receiveCurrentUser, } from '../actions'; @@ -392,28 +391,31 @@ describe( 'getAutosaves', () => { }, ]; + beforeEach( async () => { + triggerFetch.mockReset(); + } ); + it( 'yields with fetched autosaves', async () => { const postType = 'post'; const postId = 1; const restBase = 'posts'; const postEntity = { rest_base: restBase }; - const fulfillment = getAutosaves( postType, postId ); - // Trigger generator - fulfillment.next(); + triggerFetch.mockImplementation( () => SUCCESSFUL_RESPONSE ); + const dispatch = Object.assign( jest.fn(), { + receiveAutosaves: jest.fn(), + } ); + const resolveSelect = Object.assign( jest.fn(), { + getPostType: jest.fn( () => postEntity ), + } ); + await getAutosaves( postType, postId )( { dispatch, resolveSelect } ); - // Trigger generator with the postEntity and assert that correct path is formed - // in the apiFetch request. - const { value: apiFetchAction } = fulfillment.next( postEntity ); - expect( apiFetchAction.request ).toEqual( { + expect( triggerFetch ).toHaveBeenCalledWith( { path: `/wp/v2/${ restBase }/${ postId }/autosaves?context=edit`, } ); - - // Provide apiFetch response and trigger Action - const received = ( await fulfillment.next( SUCCESSFUL_RESPONSE ) ) - .value; - expect( received ).toEqual( - receiveAutosaves( 1, SUCCESSFUL_RESPONSE ) + expect( dispatch.receiveAutosaves ).toHaveBeenCalledWith( + 1, + SUCCESSFUL_RESPONSE ); } ); @@ -422,21 +424,20 @@ describe( 'getAutosaves', () => { const postId = 1; const restBase = 'posts'; const postEntity = { rest_base: restBase }; - const fulfillment = getAutosaves( postType, postId ); - // Trigger generator - fulfillment.next(); + triggerFetch.mockImplementation( () => [] ); + const dispatch = Object.assign( jest.fn(), { + receiveAutosaves: jest.fn(), + } ); + const resolveSelect = Object.assign( jest.fn(), { + getPostType: jest.fn( () => postEntity ), + } ); + await getAutosaves( postType, postId )( { dispatch, resolveSelect } ); - // Trigger generator with the postEntity and assert that correct path is formed - // in the apiFetch request. - const { value: apiFetchAction } = fulfillment.next( postEntity ); - expect( apiFetchAction.request ).toEqual( { + expect( triggerFetch ).toHaveBeenCalledWith( { path: `/wp/v2/${ restBase }/${ postId }/autosaves?context=edit`, } ); - - // Provide apiFetch response and trigger Action - const received = ( await fulfillment.next( [] ) ).value; - expect( received ).toBeUndefined(); + expect( dispatch.receiveAutosaves ).not.toHaveBeenCalled(); } ); } ); From e0203549918ac4027220f5e4728bd2eea5f18b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=83d=C4=83lin=20Gorb=C4=83nescu?= <46342490+madalingorbanescu@users.noreply.github.com> Date: Tue, 7 Sep 2021 10:40:04 +0300 Subject: [PATCH 178/214] Update `DuotonePicker` docs for accuracy(#34494) Fix `DuotonePicker` docs use of `duotonePalette` and `colorPalette`. Co-authored-by: Alex Lende <alex+github.com@lende.xyz> --- packages/components/src/duotone-picker/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/components/src/duotone-picker/README.md b/packages/components/src/duotone-picker/README.md index 16d58b016b0c51..51c12cf49ad32b 100644 --- a/packages/components/src/duotone-picker/README.md +++ b/packages/components/src/duotone-picker/README.md @@ -7,15 +7,15 @@ import { DuotonePicker, DuotoneSwatch } from '@wordpress/components'; import { useState } from '@wordpress/element'; const DUOTONE_PALETTE = [ - { colors: [ '#8c00b7', '#fcff41' ] name: 'Purple and yellow' slug: 'purple-yellow' }, - { colors: [ '#000097', '#ff4747' ] name: 'Blue and red' slug: 'blue-red' }, + { colors: [ '#8c00b7', '#fcff41' ], name: 'Purple and yellow', slug: 'purple-yellow' }, + { colors: [ '#000097', '#ff4747' ], name: 'Blue and red', slug: 'blue-red' }, ]; const COLOR_PALETTE = [ - { colors: [ '#ff4747' ] name: 'Red' slug: 'red' }, - { colors: [ '#fcff41' ] name: 'Yellow' slug: 'yellow' }, - { colors: [ '#000097' ] name: 'Blue' slug: 'blue' }, - { colors: [ '#8c00b7' ] name: 'Purple' slug: 'purple' }, + { color: '#ff4747', name: 'Red', slug: 'red' }, + { color: '#fcff41', name: 'Yellow', slug: 'yellow' }, + { color: '#000097', name: 'Blue', slug: 'blue' }, + { color: '#8c00b7', name: 'Purple', slug: 'purple' }, ]; const Example = () => { From a3da324c94304613260b0c7ce17735d406b18aa5 Mon Sep 17 00:00:00 2001 From: Mike Jolley <mike.jolley@me.com> Date: Tue, 7 Sep 2021 08:47:22 +0100 Subject: [PATCH 179/214] ComboboxControl: Only force expanded if input has focus (#34090) --- packages/components/src/combobox-control/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/components/src/combobox-control/index.js b/packages/components/src/combobox-control/index.js index 5f5eb4ef2ad3d6..9d8675aa1095f6 100644 --- a/packages/components/src/combobox-control/index.js +++ b/packages/components/src/combobox-control/index.js @@ -62,6 +62,7 @@ function ComboboxControl( { currentOption || null ); const [ isExpanded, setIsExpanded ] = useState( false ); + const [ inputHasFocus, setInputHasFocus ] = useState( false ); const [ inputValue, setInputValue ] = useState( '' ); const inputContainer = useRef(); @@ -139,7 +140,12 @@ function ComboboxControl( { } }; + const onBlur = () => { + setInputHasFocus( false ); + }; + const onFocus = () => { + setInputHasFocus( true ); setIsExpanded( true ); onFilterValueChange( '' ); setInputValue( '' ); @@ -153,7 +159,9 @@ function ComboboxControl( { const text = event.value; setInputValue( text ); onFilterValueChange( text ); - setIsExpanded( true ); + if ( inputHasFocus ) { + setIsExpanded( true ); + } }; const handleOnReset = () => { @@ -228,6 +236,7 @@ function ComboboxControl( { : null } onFocus={ onFocus } + onBlur={ onBlur } isExpanded={ isExpanded } selectedSuggestionIndex={ matchingSuggestions.indexOf( selectedSuggestion From 94620e89744bc98578d9f00d57f72b122ef9526f Mon Sep 17 00:00:00 2001 From: Brylie Christopher Oxley <brylie@amble.fi> Date: Tue, 7 Sep 2021 10:48:26 +0300 Subject: [PATCH 180/214] Remove confusing punctuation (#34322) * Remove confusing punctuation The text inside of the quote represents a WordPress version number. However, the trailing period is, while grammatically correct, confusing when reading the error output. * Update packages/env/lib/config/parse-config.js Co-authored-by: Zebulan Stanphill <zebulanstanphill@protonmail.com> Co-authored-by: Zebulan Stanphill <zebulanstanphill@protonmail.com> --- packages/env/lib/config/parse-config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/env/lib/config/parse-config.js b/packages/env/lib/config/parse-config.js index ce0b7e044e070a..bc8a8aeafa4d1e 100644 --- a/packages/env/lib/config/parse-config.js +++ b/packages/env/lib/config/parse-config.js @@ -133,7 +133,7 @@ function parseSourceString( sourceString, { workDirectoryPath } ) { } throw new ValidationError( - `Invalid or unrecognized source: "${ sourceString }."` + `Invalid or unrecognized source: "${ sourceString }".` ); } From 3c70093d2669c046a2e950330abcc936beb26f06 Mon Sep 17 00:00:00 2001 From: Mike Schroder <1034160+getsource@users.noreply.github.com> Date: Tue, 7 Sep 2021 17:04:18 +0900 Subject: [PATCH 181/214] Video Block: Use existing video poster image on insert. (#34415) * Video Block: Use existing video poster image on insert. * Removed unnecessary optional chaining. * Account for default media icon * Set `poster` to `undefined` when Removed, on Error, and when URL is changed. --- packages/block-library/src/video/edit.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index fc2887676815e9..d678b2ff21625e 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -94,12 +94,22 @@ function VideoEdit( { // in this case there was an error // previous attributes should be removed // because they may be temporary blob urls - setAttributes( { src: undefined, id: undefined } ); + setAttributes( { + src: undefined, + id: undefined, + poster: undefined, + } ); return; } + // sets the block's attribute and updates the edit component from the // selected media - setAttributes( { src: media.url, id: media.id } ); + setAttributes( { + src: media.url, + id: media.id, + poster: + media.image?.src !== media.icon ? media.image?.src : undefined, + } ); } function onSelectURL( newSrc ) { @@ -112,7 +122,7 @@ function VideoEdit( { onReplace( embedBlock ); return; } - setAttributes( { src: newSrc, id: undefined } ); + setAttributes( { src: newSrc, id: undefined, poster: undefined } ); } } @@ -151,7 +161,7 @@ function VideoEdit( { } function onRemovePoster() { - setAttributes( { poster: '' } ); + setAttributes( { poster: undefined } ); // Move focus back to the Media Upload button. posterImageButton.current.focus(); From 083e28d72b5449417086ebcad727e15d8d8e3f71 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Tue, 7 Sep 2021 16:22:01 +0800 Subject: [PATCH 182/214] Update navigation editor placeholder (#34568) * Implement placeholder * Filter out selected menu * Update tests * Fix empty dropdown when there are no other menus * Use useSelectedMenuId hook Co-authored-by: George Mamadashvili <georgemamadashvili@gmail.com> * Remove duplicate import comments * Styling adjustments * Remove duplicate comment * Use nav editor implementation of menuItemToBlockAttributes * Update dependencies Co-authored-by: George Mamadashvili <georgemamadashvili@gmail.com> --- packages/block-library/src/navigation/edit.js | 9 +- .../src/navigation/placeholder.js | 2 +- .../navigation-editor.test.js.snap | 2 +- .../experiments/navigation-editor.test.js | 15 +- .../src/components/block-placeholder/index.js | 187 ++++++++++++++++++ .../block-placeholder/menu-items-to-blocks.js | 118 +++++++++++ .../components/block-placeholder/style.scss | 54 +++++ .../use-navigation-entities.js | 142 +++++++++++++ .../add-navigation-editor-placeholder.js | 28 +++ packages/edit-navigation/src/filters/index.js | 2 + packages/edit-navigation/src/style.scss | 1 + 11 files changed, 554 insertions(+), 6 deletions(-) create mode 100644 packages/edit-navigation/src/components/block-placeholder/index.js create mode 100644 packages/edit-navigation/src/components/block-placeholder/menu-items-to-blocks.js create mode 100644 packages/edit-navigation/src/components/block-placeholder/style.scss create mode 100644 packages/edit-navigation/src/components/block-placeholder/use-navigation-entities.js create mode 100644 packages/edit-navigation/src/filters/add-navigation-editor-placeholder.js diff --git a/packages/block-library/src/navigation/edit.js b/packages/block-library/src/navigation/edit.js index 79e06d370460a2..f5d7a1c44f6563 100644 --- a/packages/block-library/src/navigation/edit.js +++ b/packages/block-library/src/navigation/edit.js @@ -103,6 +103,7 @@ function Navigation( { hasSubmenuIndicatorSetting = true, hasItemJustificationControls = true, hasColorSettings = true, + customPlaceholder: CustomPlaceholder = null, } ) { const [ isPlaceholderShown, setIsPlaceholderShown ] = useState( ! hasExistingNavItems @@ -163,7 +164,7 @@ function Navigation( { // inherit templateLock={ 'all' }. templateLock: false, __experimentalLayout: LAYOUT, - placeholder, + placeholder: ! CustomPlaceholder ? placeholder : undefined, } ); @@ -200,9 +201,13 @@ function Navigation( { } ); if ( isPlaceholderShown ) { + const PlaceholderComponent = CustomPlaceholder + ? CustomPlaceholder + : NavigationPlaceholder; + return ( <div { ...blockProps }> - <NavigationPlaceholder + <PlaceholderComponent onCreate={ ( blocks, selectNavigationBlock ) => { setIsPlaceholderShown( false ); updateInnerBlocks( blocks ); diff --git a/packages/block-library/src/navigation/placeholder.js b/packages/block-library/src/navigation/placeholder.js index a9e0e589910ce4..ce1c14533894f5 100644 --- a/packages/block-library/src/navigation/placeholder.js +++ b/packages/block-library/src/navigation/placeholder.js @@ -48,7 +48,7 @@ function NavigationPlaceholder( { onCreate }, ref ) { const { innerBlocks: blocks } = menuItemsToBlocks( menuItems ); const selectNavigationBlock = true; onCreate( blocks, selectNavigationBlock ); - } ); + }, [ menuItems, menuItemsToBlocks, onCreate ] ); const onCreateFromMenu = () => { // If we have menu items, create the block right away. diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/navigation-editor.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/navigation-editor.test.js.snap index 89f22e7ad5236c..82c208896c7dca 100644 --- a/packages/e2e-tests/specs/experiments/__snapshots__/navigation-editor.test.js.snap +++ b/packages/e2e-tests/specs/experiments/__snapshots__/navigation-editor.test.js.snap @@ -4,7 +4,7 @@ exports[`Navigation editor allows creation of a menu when there are existing men exports[`Navigation editor allows creation of a menu when there are no current menu items 1`] = ` "<!-- wp:navigation {\\"orientation\\":\\"vertical\\"} --> -<!-- wp:page-list {\\"isNavigationChild\\":true} /--> +<!-- wp:navigation-link {\\"label\\":\\"My page\\",\\"type\\":\\"page\\",\\"id\\":1,\\"url\\":\\"https://example.com/1\\",\\"isTopLevelLink\\":true} /--> <!-- /wp:navigation -->" `; diff --git a/packages/e2e-tests/specs/experiments/navigation-editor.test.js b/packages/e2e-tests/specs/experiments/navigation-editor.test.js index fa8bdf21d852ac..972ba27eb834b3 100644 --- a/packages/e2e-tests/specs/experiments/navigation-editor.test.js +++ b/packages/e2e-tests/specs/experiments/navigation-editor.test.js @@ -193,7 +193,18 @@ describe( 'Navigation editor', () => { POST: menuPostResponse, } ), ...getMenuItemMocks( { GET: [] } ), - ...getPagesMocks( { GET: [ {} ] } ), // mock a single page + ...getPagesMocks( { + GET: [ + { + type: 'page', + id: 1, + link: 'https://example.com/1', + title: { + rendered: 'My page', + }, + }, + ], + } ), ] ); await page.keyboard.type( 'Main Menu' ); @@ -354,7 +365,7 @@ describe( 'Navigation editor', () => { ); await navBlock.click(); const startEmptyButton = await page.waitForXPath( - '//button[.="Start empty"]' + '//button[.="Start blank"]' ); await startEmptyButton.click(); diff --git a/packages/edit-navigation/src/components/block-placeholder/index.js b/packages/edit-navigation/src/components/block-placeholder/index.js new file mode 100644 index 00000000000000..f4e47fa6b83dcd --- /dev/null +++ b/packages/edit-navigation/src/components/block-placeholder/index.js @@ -0,0 +1,187 @@ +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; +import { + Placeholder, + Button, + DropdownMenu, + MenuGroup, + MenuItem, + Spinner, +} from '@wordpress/components'; +import { + forwardRef, + useCallback, + useState, + useEffect, +} from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { chevronDown } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import { useMenuEntityProp, useSelectedMenuId } from '../../hooks'; +import useNavigationEntities from './use-navigation-entities'; +import menuItemsToBlocks from './menu-items-to-blocks'; + +/** + * Convert pages to blocks. + * + * @param {Object[]} pages An array of pages. + * + * @return {WPBlock[]} An array of blocks. + */ +function convertPagesToBlocks( pages ) { + if ( ! pages?.length ) { + return null; + } + + return pages.map( ( { title, type, link: url, id } ) => + createBlock( 'core/navigation-link', { + type, + id, + url, + label: ! title.rendered ? __( '(no title)' ) : title.rendered, + opensInNewTab: false, + } ) + ); +} + +const TOGGLE_PROPS = { variant: 'tertiary' }; +const POPOVER_PROPS = { position: 'bottom center' }; + +function BlockPlaceholder( { onCreate }, ref ) { + const [ selectedMenu, setSelectedMenu ] = useState(); + const [ isCreatingFromMenu, setIsCreatingFromMenu ] = useState( false ); + + const [ selectedMenuId ] = useSelectedMenuId(); + const [ menuName ] = useMenuEntityProp( 'name', selectedMenuId ); + + const { + isResolvingPages, + menus, + isResolvingMenus, + menuItems, + hasResolvedMenuItems, + pages, + hasPages, + hasMenus, + } = useNavigationEntities( selectedMenu ); + + const isLoading = isResolvingPages || isResolvingMenus; + + const createFromMenu = useCallback( () => { + const { innerBlocks: blocks } = menuItemsToBlocks( menuItems ); + const selectNavigationBlock = true; + onCreate( blocks, selectNavigationBlock ); + }, [ menuItems, menuItemsToBlocks, onCreate ] ); + + const onCreateFromMenu = () => { + // If we have menu items, create the block right away. + if ( hasResolvedMenuItems ) { + createFromMenu(); + return; + } + + // Otherwise, create the block when resolution finishes. + setIsCreatingFromMenu( true ); + }; + + const onCreateEmptyMenu = () => { + onCreate( [] ); + }; + + const onCreateAllPages = () => { + const blocks = convertPagesToBlocks( pages ); + const selectNavigationBlock = true; + onCreate( blocks, selectNavigationBlock ); + }; + + useEffect( () => { + // If the user selected a menu but we had to wait for menu items to + // finish resolving, then create the block once resolution finishes. + if ( isCreatingFromMenu && hasResolvedMenuItems ) { + createFromMenu(); + setIsCreatingFromMenu( false ); + } + }, [ isCreatingFromMenu, hasResolvedMenuItems ] ); + + const selectableMenus = menus?.filter( + ( menu ) => menu.id !== selectedMenuId + ); + + const hasSelectableMenus = !! selectableMenus?.length; + + return ( + <Placeholder + className="edit-navigation-block-placeholder" + label={ menuName } + instructions={ __( + 'This menu is empty. You can start blank and choose what to add,' + + ' add your existing pages, or add the content of another menu.' + ) } + > + <div className="edit-navigation-block-placeholder__controls"> + { isLoading && ( + <div ref={ ref }> + <Spinner /> + </div> + ) } + { ! isLoading && ( + <div + ref={ ref } + className="edit-navigation-block-placeholder__actions" + > + <Button + variant="tertiary" + onClick={ onCreateEmptyMenu } + > + { __( 'Start blank' ) } + </Button> + { hasPages ? ( + <Button + variant={ hasMenus ? 'tertiary' : 'primary' } + onClick={ onCreateAllPages } + > + { __( 'Add all pages' ) } + </Button> + ) : undefined } + { hasSelectableMenus ? ( + <DropdownMenu + text={ __( 'Copy existing menu' ) } + icon={ chevronDown } + toggleProps={ TOGGLE_PROPS } + popoverProps={ POPOVER_PROPS } + > + { ( { onClose } ) => ( + <MenuGroup> + { selectableMenus.map( ( menu ) => { + return ( + <MenuItem + onClick={ () => { + setSelectedMenu( + menu.id + ); + onCreateFromMenu(); + } } + onClose={ onClose } + key={ menu.id } + > + { menu.name } + </MenuItem> + ); + } ) } + </MenuGroup> + ) } + </DropdownMenu> + ) : undefined } + </div> + ) } + </div> + </Placeholder> + ); +} + +export default forwardRef( BlockPlaceholder ); diff --git a/packages/edit-navigation/src/components/block-placeholder/menu-items-to-blocks.js b/packages/edit-navigation/src/components/block-placeholder/menu-items-to-blocks.js new file mode 100644 index 00000000000000..db29190438447c --- /dev/null +++ b/packages/edit-navigation/src/components/block-placeholder/menu-items-to-blocks.js @@ -0,0 +1,118 @@ +/** + * External dependencies + */ +import { sortBy } from 'lodash'; + +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { menuItemToBlockAttributes } from '../../store/utils'; + +/** + * Convert a flat menu item structure to a nested blocks structure. + * + * @param {Object[]} menuItems An array of menu items. + * + * @return {WPBlock[]} An array of blocks. + */ +export default function menuItemsToBlocks( menuItems ) { + if ( ! menuItems ) { + return null; + } + + const menuTree = createDataTree( menuItems ); + return mapMenuItemsToBlocks( menuTree ); +} + +/** @typedef {import('../..store/utils').WPNavMenuItem} WPNavMenuItem */ + +/** + * A recursive function that maps menu item nodes to blocks. + * + * @param {WPNavMenuItem[]} menuItems An array of WPNavMenuItem items. + * @return {Object} Object containing innerBlocks and mapping. + */ +function mapMenuItemsToBlocks( menuItems ) { + let mapping = {}; + + // The menuItem should be in menu_order sort order. + const sortedItems = sortBy( menuItems, 'menu_order' ); + + const innerBlocks = sortedItems.map( ( menuItem ) => { + const attributes = menuItemToBlockAttributes( menuItem ); + + // If there are children recurse to build those nested blocks. + const { + innerBlocks: nestedBlocks = [], // alias to avoid shadowing + mapping: nestedMapping = {}, // alias to avoid shadowing + } = menuItem.children?.length + ? mapMenuItemsToBlocks( menuItem.children ) + : {}; + + // Update parent mapping with nested mapping. + mapping = { + ...mapping, + ...nestedMapping, + }; + + // Create block with nested "innerBlocks". + const block = createBlock( + 'core/navigation-link', + attributes, + nestedBlocks + ); + + // Create mapping for menuItem -> block + mapping[ menuItem.id ] = block.clientId; + + return block; + } ); + + return { + innerBlocks, + mapping, + }; +} + +/** + * Creates a nested, hierarchical tree representation from unstructured data that + * has an inherent relationship defined between individual items. + * + * For example, by default, each element in the dataset should have an `id` and + * `parent` property where the `parent` property indicates a relationship between + * the current item and another item with a matching `id` properties. + * + * This is useful for building linked lists of data from flat data structures. + * + * @param {Array} dataset linked data to be rearranged into a hierarchical tree based on relational fields. + * @param {string} id the property which uniquely identifies each entry within the array. + * @param {*} relation the property which identifies how the current item is related to other items in the data (if at all). + * @return {Array} a nested array of parent/child relationships + */ +function createDataTree( dataset, id = 'id', relation = 'parent' ) { + const hashTable = Object.create( null ); + const dataTree = []; + + for ( const data of dataset ) { + hashTable[ data[ id ] ] = { + ...data, + children: [], + }; + } + for ( const data of dataset ) { + if ( data[ relation ] ) { + hashTable[ data[ relation ] ].children.push( + hashTable[ data[ id ] ] + ); + } else { + dataTree.push( hashTable[ data[ id ] ] ); + } + } + + return dataTree; +} diff --git a/packages/edit-navigation/src/components/block-placeholder/style.scss b/packages/edit-navigation/src/components/block-placeholder/style.scss new file mode 100644 index 00000000000000..bd9b6eab7c9a93 --- /dev/null +++ b/packages/edit-navigation/src/components/block-placeholder/style.scss @@ -0,0 +1,54 @@ +.edit-navigation-block-placeholder { + // The navigation editor already has a border around content. + // Hide the placeholder's border. Requires extra specificity. + &.edit-navigation-block-placeholder { + box-shadow: none; + background: transparent; + + @include break-medium() { + margin: -$grid-unit-20 0; + } + } + + // Show placeholder instructions when it's a medium size. + &.is-medium .components-placeholder__instructions { + display: block; + } + + // Display buttons in a column when placeholder is small. + .edit-navigation-block-placeholder__actions { + display: flex; + flex-direction: column; + align-items: flex-start; + + .components-button { + margin-bottom: $grid-unit-05; + margin-right: 0; + + // Avoid bottom margin on the dropdown since it makes the + // menu anchor itself too far away from the button. + &.components-dropdown-menu__toggle { + margin-bottom: 0; + + svg { + // Make the spacing inside the left of the button match the + // spacing inside the right of the button. + margin-left: -6px; + } + } + } + } + + @include break-medium() { + .edit-navigation-block-placeholder__actions { + flex-direction: row; + } + + // Change the default button margin. Again use extra specificity. + &.edit-navigation-block-placeholder.is-medium .components-button { + margin-bottom: 0; + margin-right: $grid-unit-15; + } + } + +} diff --git a/packages/edit-navigation/src/components/block-placeholder/use-navigation-entities.js b/packages/edit-navigation/src/components/block-placeholder/use-navigation-entities.js new file mode 100644 index 00000000000000..17806deadd9a81 --- /dev/null +++ b/packages/edit-navigation/src/components/block-placeholder/use-navigation-entities.js @@ -0,0 +1,142 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; + +/** + * @typedef {Object} NavigationEntitiesData + * @property {Array|undefined} pages - a collection of WP Post entity objects of post type "Page". + * @property {boolean} isResolvingPages - indicates whether the request to fetch pages is currently resolving. + * @property {boolean} hasResolvedPages - indicates whether the request to fetch pages has finished resolving. + * @property {Array|undefined} menus - a collection of Menu entity objects. + * @property {boolean} isResolvingMenus - indicates whether the request to fetch menus is currently resolving. + * @property {boolean} hasResolvedMenus - indicates whether the request to fetch menus has finished resolving. + * @property {Array|undefined} menusItems - a collection of Menu Item entity objects for the current menuId. + * @property {boolean} hasResolvedMenuItems - indicates whether the request to fetch menuItems has finished resolving. + * @property {boolean} hasPages - indicates whether there is currently any data for pages. + * @property {boolean} hasMenus - indicates whether there is currently any data for menus. + */ + +/** + * Manages fetching and resolution state for all entities required + * for the Navigation block. + * + * @param {number} menuId the menu for which to retrieve menuItem data. + * @return { NavigationEntitiesData } the entity data. + */ +export default function useNavigationEntities( menuId ) { + return { + ...usePageEntities(), + ...useMenuEntities(), + ...useMenuItemEntities( menuId ), + }; +} + +function useMenuEntities() { + const { menus, isResolvingMenus, hasResolvedMenus } = useSelect( + ( select ) => { + const { getMenus, isResolving, hasFinishedResolution } = select( + coreStore + ); + + const menusParameters = [ { per_page: -1 } ]; + + return { + menus: getMenus( ...menusParameters ), + isResolvingMenus: isResolving( 'getMenus', menusParameters ), + hasResolvedMenus: hasFinishedResolution( + 'getMenus', + menusParameters + ), + }; + }, + [] + ); + + return { + menus, + isResolvingMenus, + hasResolvedMenus, + hasMenus: !! ( hasResolvedMenus && menus?.length ), + }; +} + +function useMenuItemEntities( menuId ) { + const { menuItems, hasResolvedMenuItems } = useSelect( + ( select ) => { + const { getMenuItems, hasFinishedResolution } = select( coreStore ); + + const hasSelectedMenu = menuId !== undefined; + const menuItemsParameters = hasSelectedMenu + ? [ + { + menus: menuId, + per_page: -1, + }, + ] + : undefined; + + return { + menuItems: hasSelectedMenu + ? getMenuItems( ...menuItemsParameters ) + : undefined, + hasResolvedMenuItems: hasSelectedMenu + ? hasFinishedResolution( + 'getMenuItems', + menuItemsParameters + ) + : false, + }; + }, + [ menuId ] + ); + + return { + menuItems, + hasResolvedMenuItems, + }; +} + +function usePageEntities() { + const { pages, isResolvingPages, hasResolvedPages } = useSelect( + ( select ) => { + const { + getEntityRecords, + isResolving, + hasFinishedResolution, + } = select( coreStore ); + + const pagesParameters = [ + 'postType', + 'page', + { + parent: 0, + order: 'asc', + orderby: 'id', + per_page: -1, + }, + ]; + + return { + pages: getEntityRecords( ...pagesParameters ) || null, + isResolvingPages: isResolving( + 'getEntityRecords', + pagesParameters + ), + hasResolvedPages: hasFinishedResolution( + 'getEntityRecords', + pagesParameters + ), + }; + }, + [] + ); + + return { + pages, + isResolvingPages, + hasResolvedPages, + hasPages: !! ( hasResolvedPages && pages?.length ), + }; +} diff --git a/packages/edit-navigation/src/filters/add-navigation-editor-placeholder.js b/packages/edit-navigation/src/filters/add-navigation-editor-placeholder.js new file mode 100644 index 00000000000000..263658d0ffc646 --- /dev/null +++ b/packages/edit-navigation/src/filters/add-navigation-editor-placeholder.js @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +/** + * Internal dependencies + */ +import { addFilter } from '@wordpress/hooks'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import BlockPlaceholder from '../components/block-placeholder'; + +const addNavigationEditorPlaceholder = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + if ( props.name !== 'core/navigation' ) { + return <BlockEdit { ...props } />; + } + return ( + <BlockEdit { ...props } customPlaceholder={ BlockPlaceholder } /> + ); + }, + 'withNavigationEditorPlaceholder' +); + +export default () => + addFilter( + 'editor.BlockEdit', + 'core/edit-navigation/with-navigation-editor-placeholder', + addNavigationEditorPlaceholder + ); diff --git a/packages/edit-navigation/src/filters/index.js b/packages/edit-navigation/src/filters/index.js index 96315b46b7eb8d..08ab87f0fe4b5d 100644 --- a/packages/edit-navigation/src/filters/index.js +++ b/packages/edit-navigation/src/filters/index.js @@ -1,6 +1,7 @@ /** * Internal dependencies */ +import addNavigationEditorPlaceholder from './add-navigation-editor-placeholder'; import addMenuNameEditor from './add-menu-name-editor'; import disableInsertingNonNavigationBlocks from './disable-inserting-non-navigation-blocks'; import removeEditUnsupportedFeatures from './remove-edit-unsupported-features'; @@ -9,6 +10,7 @@ import removeSettingsUnsupportedFeatures from './remove-settings-unsupported-fea export const addFilters = ( shouldAddDisableInsertingNonNavigationBlocksFilter ) => { + addNavigationEditorPlaceholder(); addMenuNameEditor(); if ( shouldAddDisableInsertingNonNavigationBlocksFilter ) { disableInsertingNonNavigationBlocks(); diff --git a/packages/edit-navigation/src/style.scss b/packages/edit-navigation/src/style.scss index d3865a1dfff8ea..ef8c1bdea66467 100644 --- a/packages/edit-navigation/src/style.scss +++ b/packages/edit-navigation/src/style.scss @@ -8,6 +8,7 @@ $navigation-editor-spacing-top: $grid-unit-50 * 2; } @import "./components/add-menu/style.scss"; +@import "./components/block-placeholder/style.scss"; @import "../../interface/src/style.scss"; @import "./components/editor/style.scss"; @import "./components/error-boundary/style.scss"; From 5b2f0a95de5cc6795ce854ad4b6d5569c9eeafe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 7 Sep 2021 10:34:03 +0200 Subject: [PATCH 183/214] Remove duplicated useValidAlignment hook (#34593) --- packages/block-editor/src/hooks/align.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js index b1e68b68bfe260..c72b1aa29f796a 100644 --- a/packages/block-editor/src/hooks/align.js +++ b/packages/block-editor/src/hooks/align.js @@ -125,9 +125,6 @@ export const withToolbarControls = createHigherOrderComponent( getBlockSupport( blockName, 'align' ), hasBlockSupport( blockName, 'alignWide', true ) ); - const validAlignments = useAvailableAlignments( - blockAllowedAlignments - ); const updateAlignment = ( nextAlign ) => { if ( ! nextAlign ) { @@ -142,12 +139,12 @@ export const withToolbarControls = createHigherOrderComponent( return ( <> - { validAlignments.length > 0 && ( + { blockAllowedAlignments.length > 0 && ( <BlockControls group="block" __experimentalExposeToChildren> <BlockAlignmentControl value={ props.attributes.align } onChange={ updateAlignment } - controls={ validAlignments } + controls={ blockAllowedAlignments } /> </BlockControls> ) } From bd41d5084ca80b1af805bbd7465fffe2c3aa8ccf Mon Sep 17 00:00:00 2001 From: George Mamadashvili <georgemamadashvili@gmail.com> Date: Tue, 7 Sep 2021 08:46:43 +0000 Subject: [PATCH 184/214] Documentation: Replace withSelect references with useSelect (#34549) * Documentation: Replace withSelect references with useSelect * Update toolbar button examples * Update: Initialize the Input Control section * Updates "sidebar update meta" section * Remove finishing touches --- .../block-tutorial/creating-dynamic-blocks.md | 34 ++- .../format-api/2-toolbar-button.md | 63 +++--- .../sidebar-tutorial/plugin-sidebar-0.md | 1 - .../plugin-sidebar-4-initialize-input.md | 41 ++-- .../plugin-sidebar-5-update-meta.md | 52 ++--- .../plugin-sidebar-6-finishing-touches.md | 199 ------------------ docs/manifest.json | 6 - docs/toc.json | 3 - 8 files changed, 71 insertions(+), 328 deletions(-) delete mode 100644 docs/how-to-guides/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md diff --git a/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md b/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md index bf86d303fc643e..770fa53ec7c154 100644 --- a/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md +++ b/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md @@ -22,7 +22,7 @@ The following code example shows how to create a dynamic block that shows only t ```jsx import { registerBlockType } from '@wordpress/blocks'; -import { withSelect } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { useBlockProps } from '@wordpress/block-editor'; registerBlockType( 'gutenberg-examples/example-dynamic', { @@ -31,12 +31,11 @@ registerBlockType( 'gutenberg-examples/example-dynamic', { icon: 'megaphone', category: 'widgets', - edit: withSelect( ( select ) => { - return { - posts: select( 'core' ).getEntityRecords( 'postType', 'post' ), - }; - } )( ( { posts } ) => { + edit: () => { const blockProps = useBlockProps(); + const posts = useSelect( ( select ) => { + return select( 'core' ).getEntityRecords( 'postType', 'post' ); + }, [] ); return ( <div { ...blockProps }> @@ -49,7 +48,7 @@ registerBlockType( 'gutenberg-examples/example-dynamic', { ) } </div> ); - } ), + }, } ); ``` @@ -59,7 +58,7 @@ registerBlockType( 'gutenberg-examples/example-dynamic', { ( function ( blocks, element, data, blockEditor ) { var el = element.createElement, registerBlockType = blocks.registerBlockType, - withSelect = data.withSelect, + useSelect = data.useSelect, useBlockProps = blockEditor.useBlockProps; registerBlockType( 'gutenberg-examples/example-dynamic', { @@ -67,24 +66,23 @@ registerBlockType( 'gutenberg-examples/example-dynamic', { title: 'Example: last post', icon: 'megaphone', category: 'widgets', - edit: withSelect( function ( select ) { - return { - posts: select( 'core' ).getEntityRecords( 'postType', 'post' ), - }; - } )( function ( props ) { - var blockProps = useBlockProps(); + edit: function () { var content; - if ( ! props.posts ) { + var blockProps = useBlockProps(); + var posts = useSelect( function ( select ) { + return select( 'core' ).getEntityRecords( 'postType', 'post' ); + }, [] ); + if ( ! posts ) { content = 'Loading...'; - } else if ( props.posts.length === 0 ) { + } else if ( posts.length === 0 ) { content = 'No posts'; } else { - var post = props.posts[ 0 ]; + var post = posts[ 0 ]; content = el( 'a', { href: post.link }, post.title.rendered ); } return el( 'div', blockProps, content ); - } ), + }, } ); } )( window.wp.blocks, diff --git a/docs/how-to-guides/format-api/2-toolbar-button.md b/docs/how-to-guides/format-api/2-toolbar-button.md index ddc10df8c9877a..5cbfabbdec05e1 100644 --- a/docs/how-to-guides/format-api/2-toolbar-button.md +++ b/docs/how-to-guides/format-api/2-toolbar-button.md @@ -74,31 +74,26 @@ The following sample code renders the previously shown button only on Paragraph ```js ( function ( wp ) { - var withSelect = wp.data.withSelect; - var ifCondition = wp.compose.ifCondition; - var compose = wp.compose.compose; - var MyCustomButton = function ( props ) { - return wp.element.createElement( wp.editor.RichTextToolbarButton, { + var el = wp.element.createElement; + var useSelect = wp.data.useSelect; + + function ConditionalButton( props ) { + var selectedBlock = useSelect( function ( select ) { + return select( 'core/block-editor' ).getSelectedBlock(); + }, [] ); + + if ( selectedBlock && selectedBlock.name !== 'core/paragraph' ) { + return null; + } + + return el( wp.blockEditor.RichTextToolbarButton, { icon: 'editor-code', title: 'Sample output', onClick: function () { - console.log( 'toggle format' ); + console.log( 'toggle format!' ); }, } ); }; - var ConditionalButton = compose( - withSelect( function ( select ) { - return { - selectedBlock: select( 'core/editor' ).getSelectedBlock(), - }; - } ), - ifCondition( function ( props ) { - return ( - props.selectedBlock && - props.selectedBlock.name === 'core/paragraph' - ); - } ) - )( MyCustomButton ); wp.richText.registerFormatType( 'my-custom-format/sample-output', { title: 'Sample output', @@ -112,12 +107,19 @@ The following sample code renders the previously shown button only on Paragraph {% ESNext %} ```js -import { compose, ifCondition } from '@wordpress/compose'; import { registerFormatType } from '@wordpress/rich-text'; import { RichTextToolbarButton } from '@wordpress/block-editor'; -import { withSelect } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; + +function ConditionalButton( props ) { + const selectedBlock = useSelect( ( select ) => { + return select( 'core/block-editor' ).getSelectedBlock(); + }, [] ); + + if ( selectedBlock && selectedBlock.name !== 'core/paragraph' ) { + return null; + } -const MyCustomButton = ( props ) => { return ( <RichTextToolbarButton icon="editor-code" @@ -127,20 +129,7 @@ const MyCustomButton = ( props ) => { } } /> ); -}; - -const ConditionalButton = compose( - withSelect( function ( select ) { - return { - selectedBlock: select( 'core/editor' ).getSelectedBlock(), - }; - } ), - ifCondition( function ( props ) { - return ( - props.selectedBlock && props.selectedBlock.name === 'core/paragraph' - ); - } ) -)( MyCustomButton ); +} registerFormatType( 'my-custom-format/sample-output', { title: 'Sample output', @@ -152,6 +141,6 @@ registerFormatType( 'my-custom-format/sample-output', { {% end %} -Don't forget adding `wp-compose` and `wp-data` to the dependencies array in the PHP script. +Don't forget adding `wp-data` to the dependencies array in the PHP script. More advanced conditions can be used, e.g., only render the button depending on specific attributes of the block. diff --git a/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md index 750a12236d6605..3a6d87c7a50b82 100644 --- a/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md +++ b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md @@ -9,4 +9,3 @@ In the next sections, you're going to create a custom sidebar for a plugin that 3. [Register a new meta field](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-3-register-meta.md) 4. [Initialize the input control with the meta field value](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-4-initialize-input.md) 5. [Update the meta field value when input's content changes](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-5-update-meta.md) -6. [Finishing touches](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md) diff --git a/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-4-initialize-input.md b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-4-initialize-input.md index 106bd31925e44a..1df2b7bfb36753 100644 --- a/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-4-initialize-input.md +++ b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-4-initialize-input.md @@ -41,17 +41,9 @@ Now that the field is available in the editor store, it can be surfaced to the U Now you can focus solely on the `MetaBlockField` component. The goal is to initialize it with the value of `sidebar_plugin_meta_block_field`, but also to keep it updated when that value changes. -WordPress has [some utilities to work with data](/packages/data/README.md) from the stores. The first you're going to use is [withSelect](/packages/data/README.md#withselect-mapselecttoprops-function-function), whose signature is: +WordPress has [some utilities to work with data](/packages/data/README.md) from the stores. The first you're going to use is [useSelect](/packages/data/README.md#useselect). -```js -withSelect()(); -// a function that takes `select` as input -// and returns an object containing data -// a function that takes the previous data as input -// and returns a component -``` - -`withSelect` is used to pass data to other components, and update them when the original data changes. Let's update the code to use it: +The `useSelect` is used to fetch data for the current component and update it when the original data changes. Let's update the code to use it: ```js ( function ( wp ) { @@ -59,30 +51,22 @@ withSelect()(); var PluginSidebar = wp.editPost.PluginSidebar; var el = wp.element.createElement; var Text = wp.components.TextControl; - var withSelect = wp.data.withSelect; - - var mapSelectToProps = function ( select ) { - return { - metaFieldValue: select( 'core/editor' ).getEditedPostAttribute( - 'meta' - )[ 'sidebar_plugin_meta_block_field' ], - }; - }; + var useSelect = wp.data.useSelect; + + var MetaBlockField = function () { + var metaFieldValue = useSelect( function ( select ) { + return select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ 'sidebar_plugin_meta_block_field' ]; + }, [] ); - var MetaBlockField = function ( props ) { return el( Text, { label: 'Meta Block Field', - value: props.metaFieldValue, + value: metaFieldValue, onChange: function ( content ) { console.log( 'content has changed to ', content ); }, } ); }; - var MetaBlockFieldWithData = withSelect( mapSelectToProps )( - MetaBlockField - ); - registerPlugin( 'my-plugin-sidebar', { render: function () { return el( @@ -95,7 +79,7 @@ withSelect()(); el( 'div', { className: 'plugin-sidebar-content' }, - el( MetaBlockFieldWithData ) + el( MetaBlockField ) ) ); }, @@ -103,12 +87,11 @@ withSelect()(); } )( window.wp ); ``` -Copy this code to the JavaScript file. Note that it now uses the `wp.data.withSelect` utility to be found in the `@wordpress/data` package. Go ahead and add `wp-data` as a dependency in the PHP script. +Copy this code to the JavaScript file. Note that it now uses the `wp.data.useSelect` utility to be found in the `@wordpress/data` package. Go ahead and add `wp-data` as a dependency in the PHP script. This is how the code changes from the previous section: -- The `MetaBlockField` function has now a `props` argument as input. It contains the data object returned by the `mapSelectToProps` function, which it uses to initialize its value property. -- The component rendered within the `div` element was also updated, the plugin now uses `MetaBlockFieldWithData`. This will be updated every time the original data changes. +- The `MetaBlockField` component will be updated every time the original data changes. - [getEditedPostAttribute](/docs/reference-guides/data/data-core-editor.md#geteditedpostattribute) is used to retrieve data instead of [getCurrentPost](/docs/reference-guides/data/data-core-editor.md#getcurrentpost) because it returns the most recent values of the post, including user editions that haven't been yet saved. Update the code and open the sidebar. The input's content is no longer `Initial value` but a void string. Users can't type values yet, but let's check that the component is updated if the value in the store changes. Open the browser's console, execute diff --git a/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-5-update-meta.md b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-5-update-meta.md index 4fadd285f69423..9a5a8c8be78dfd 100644 --- a/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-5-update-meta.md +++ b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-5-update-meta.md @@ -1,8 +1,8 @@ # Update the Meta Field When the Input's Content Changes -The last step in the journey is to update the meta field when the input content changes. To do that, you'll use another utility from the `@wordpress/data` package, [withDispatch](/packages/data/README.md#withdispatch-mapdispatchtoprops-function-function). +The last step in the journey is to update the meta field when the input content changes. To do that, you'll use another utility from the `@wordpress/data` package, [useDispatch](/packages/data/README.md#usedispatch). These utilities are also known as _hooks_. -`withDispatch` works similarly to `withSelect`. It takes two functions, the first returns an object with data, and the second takes that data object as input and returns a new UI component. Let's see how to use it: +The `useDispatch` takes store name as its only argument and returns methods that you can use to update the store. ```js ( function ( wp ) { @@ -10,44 +10,27 @@ The last step in the journey is to update the meta field when the input content var PluginSidebar = wp.editPost.PluginSidebar; var el = wp.element.createElement; var Text = wp.components.TextControl; - var withSelect = wp.data.withSelect; - var withDispatch = wp.data.withDispatch; - - var mapSelectToProps = function ( select ) { - return { - metaFieldValue: select( 'core/editor' ).getEditedPostAttribute( - 'meta' - )[ 'sidebar_plugin_meta_block_field' ], - }; - }; - - var mapDispatchToProps = function ( dispatch ) { - return { - setMetaFieldValue: function ( value ) { - dispatch( 'core/editor' ).editPost( { - meta: { sidebar_plugin_meta_block_field: value }, - } ); - }, - }; - }; + var useSelect = wp.data.useSelect; + var useDispatch = wp.data.useDispatch; var MetaBlockField = function ( props ) { + var metaFieldValue = useSelect( function ( select ) { + return select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ 'sidebar_plugin_meta_block_field' ]; + }, [] ); + + var editPost = useDispatch( 'core/editor' ).editPost; + return el( Text, { label: 'Meta Block Field', - value: props.metaFieldValue, + value: metaFieldValue, onChange: function ( content ) { - props.setMetaFieldValue( content ); + editPost( { + meta: { sidebar_plugin_meta_block_field: content }, + } ); }, } ); }; - var MetaBlockFieldWithData = withSelect( mapSelectToProps )( - MetaBlockField - ); - var MetaBlockFieldWithDataAndActions = withDispatch( mapDispatchToProps )( - MetaBlockFieldWithData - ); - registerPlugin( 'my-plugin-sidebar', { render: function () { return el( @@ -60,7 +43,7 @@ The last step in the journey is to update the meta field when the input content el( 'div', { className: 'plugin-sidebar-content' }, - el( MetaBlockFieldWithDataAndActions ) + el( MetaBlockField ) ) ); }, @@ -70,9 +53,8 @@ The last step in the journey is to update the meta field when the input content Here's how it changed from the previous section: -- Added a new `mapDispatchToProps` function that will be passed to `withDispatch`. It takes `dispatch` as input and returns an object containing functions to update the internal data structures of the editor. These functions are also known as _actions_. -- By calling `setMetaFieldValue` every time the user types something within the input control, we're effectively updating the editor store on each key stroke. -- The `props` argument to the `MetaBlockField` component contains now the data passed by `mapSelectToProps` and the actions passed by `mapDispatchToProps`. +- The component now use `editPost` function returned by `useDispatch` hook. These functions are also known as _actions_. +- By calling `editPost` every time the user types something within the input control, we're effectively updating the editor store on each key stroke. Copy this new code to the JavaScript file, load the sidebar and see how the input value gets updated as you type. You may want to check that the internal data structures are updated as well. Type something in the input control, and execute the following instruction in your browser's console: diff --git a/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md b/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md deleted file mode 100644 index cd2d2f2d45c018..00000000000000 --- a/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md +++ /dev/null @@ -1,199 +0,0 @@ -# Finishing Touches - -Your JavaScript code now works as expected, here are a few ways to simplify and make it easier to change in the future. - -The first step is to convert the functions `mapSelectToProps` and `mapDispatchToProps` to anonymous functions that get passed directly to `withSelect` and `withData`, respectively: - -```js -( function ( wp ) { - var registerPlugin = wp.plugins.registerPlugin; - var PluginSidebar = wp.editPost.PluginSidebar; - var el = wp.element.createElement; - var Text = wp.components.TextControl; - var withSelect = wp.data.withSelect; - var withDispatch = wp.data.withDispatch; - - var MetaBlockField = function ( props ) { - return el( Text, { - label: 'Meta Block Field', - value: props.metaFieldValue, - onChange: function ( content ) { - props.setMetaFieldValue( content ); - }, - } ); - }; - - var MetaBlockFieldWithData = withSelect( function ( select ) { - return { - metaFieldValue: select( 'core/editor' ).getEditedPostAttribute( - 'meta' - )[ 'sidebar_plugin_meta_block_field' ], - }; - } )( MetaBlockField ); - - var MetaBlockFieldWithDataAndActions = withDispatch( function ( dispatch ) { - return { - setMetaFieldValue: function ( value ) { - dispatch( 'core/editor' ).editPost( { - meta: { sidebar_plugin_meta_block_field: value }, - } ); - }, - }; - } )( MetaBlockFieldWithData ); - - registerPlugin( 'my-plugin-sidebar', { - render: function () { - return el( - PluginSidebar, - { - name: 'my-plugin-sidebar', - icon: 'admin-post', - title: 'My plugin sidebar', - }, - el( - 'div', - { className: 'plugin-sidebar-content' }, - el( MetaBlockFieldWithDataAndActions ) - ) - ); - }, - } ); -} )( window.wp ); -``` - -Next, merge `MetaBlockField`, `MetaBlockFieldWithData`, and `MetaBlockFieldWithDataAndActions` into one function called `MetaBlockField` that gets passed to the `div` element. The `@wordpress/compose` package offers an utility to concatenate functions called `compose`. Don't forget adding `wp-compose` to the dependencies array in the PHP script. - -```js -( function ( wp ) { - var registerPlugin = wp.plugins.registerPlugin; - var PluginSidebar = wp.editPost.PluginSidebar; - var el = wp.element.createElement; - var Text = wp.components.TextControl; - var withSelect = wp.data.withSelect; - var withDispatch = wp.data.withDispatch; - var compose = wp.compose.compose; - - var MetaBlockField = compose( - withDispatch( function ( dispatch ) { - return { - setMetaFieldValue: function ( value ) { - dispatch( 'core/editor' ).editPost( { - meta: { sidebar_plugin_meta_block_field: value }, - } ); - }, - }; - } ), - withSelect( function ( select ) { - return { - metaFieldValue: select( 'core/editor' ).getEditedPostAttribute( - 'meta' - )[ 'sidebar_plugin_meta_block_field' ], - }; - } ) - )( function ( props ) { - return el( Text, { - label: 'Meta Block Field', - value: props.metaFieldValue, - onChange: function ( content ) { - props.setMetaFieldValue( content ); - }, - } ); - } ); - - registerPlugin( 'my-plugin-sidebar', { - render: function () { - return el( - PluginSidebar, - { - name: 'my-plugin-sidebar', - icon: 'admin-post', - title: 'My plugin sidebar', - }, - el( - 'div', - { className: 'plugin-sidebar-content' }, - el( MetaBlockField ) - ) - ); - }, - } ); -} )( window.wp ); -``` - -Finally, extract the meta field name (`sidebar_plugin_meta_block_field`) from the `withSelect` and `withDispatch` functions to a single place, so it's easier to change in the future. You can leverage the fact that `withSelect` and `withDispatch` first functions can take the props of the UI component they wrap as a second argument. For example: - -```js -// ... -var MetaBlockFieldWithData = withSelect( function ( select, props ) { - console.log( props.metaFieldName ); -} )( MetaBlockField ); - -// ... -el( MetaBlockFieldWithData, { - metaFieldName: 'sidebar_plugin_meta_block_field', -} ); -// ... -``` - -Notice how the `metaFieldName` can be accessed within `withSelect`. Let's change the code to take advantage of that: - -```js -( function ( wp ) { - var registerPlugin = wp.plugins.registerPlugin; - var PluginSidebar = wp.editPost.PluginSidebar; - var el = wp.element.createElement; - var Text = wp.components.TextControl; - var withSelect = wp.data.withSelect; - var withDispatch = wp.data.withDispatch; - var compose = wp.compose.compose; - - var MetaBlockField = compose( - withDispatch( function ( dispatch, props ) { - return { - setMetaFieldValue: function ( value ) { - dispatch( 'core/editor' ).editPost( { - meta: { [ props.fieldName ]: value }, - } ); - }, - }; - } ), - withSelect( function ( select, props ) { - return { - metaFieldValue: select( 'core/editor' ).getEditedPostAttribute( - 'meta' - )[ props.fieldName ], - }; - } ) - )( function ( props ) { - return el( Text, { - label: 'Meta Block Field', - value: props.metaFieldValue, - onChange: function ( content ) { - props.setMetaFieldValue( content ); - }, - } ); - } ); - - registerPlugin( 'my-plugin-sidebar', { - render: function () { - return el( - PluginSidebar, - { - name: 'my-plugin-sidebar', - icon: 'admin-post', - title: 'My plugin sidebar', - }, - el( - 'div', - { className: 'plugin-sidebar-content' }, - el( MetaBlockField, { - fieldName: 'sidebar_plugin_meta_block_field', - } ) - ) - ); - }, - } ); -} )( window.wp ); -``` - -That's it. You have now a compact version of the original code. Go ahead and add more functionality to your plugin, review other tutorials, or create your next gig! diff --git a/docs/manifest.json b/docs/manifest.json index 5fdf1d29c05a82..4264870ae044a4 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -239,12 +239,6 @@ "markdown_source": "../docs/how-to-guides/sidebar-tutorial/plugin-sidebar-5-update-meta.md", "parent": "plugin-sidebar-0" }, - { - "title": "Finishing Touches", - "slug": "plugin-sidebar-6-finishing-touches", - "markdown_source": "../docs/how-to-guides/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md", - "parent": "plugin-sidebar-0" - }, { "title": "Blocks", "slug": "block-tutorial", diff --git a/docs/toc.json b/docs/toc.json index 9d86afa1eb7f0e..875e5149015174 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -98,9 +98,6 @@ }, { "docs/how-to-guides/sidebar-tutorial/plugin-sidebar-5-update-meta.md": [] - }, - { - "docs/how-to-guides/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md": [] } ] }, From 491eb8aad117ea982f8811209689a47f98522824 Mon Sep 17 00:00:00 2001 From: Marco Ciampini <marco.ciampo@gmail.com> Date: Tue, 7 Sep 2021 11:36:47 +0200 Subject: [PATCH 185/214] Components: fix `ToggleGroupControlBackdrop` not updating size when `isAdaptiveWidth` prop changes (#34595) --- packages/components/CHANGELOG.md | 1 + .../components/src/toggle-group-control/component.tsx | 9 +++++---- .../toggle-group-control-backdrop.tsx | 3 ++- packages/components/src/toggle-group-control/types.ts | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 90d083445fab2d..4d943b62a9d921 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -10,6 +10,7 @@ - Fixed RTL styles in `Flex` component ([#33729](https://github.com/WordPress/gutenberg/pull/33729)). - Fixed unit test errors caused by `CSS.supports` being called in a non-browser environment ([#34572](https://github.com/WordPress/gutenberg/pull/34572)). +- Fixed `ToggleGroupControl`'s backdrop not updating when changing the `isAdaptiveWidth` property ([#34595](https://github.com/WordPress/gutenberg/pull/34595)). ### Internal diff --git a/packages/components/src/toggle-group-control/component.tsx b/packages/components/src/toggle-group-control/component.tsx index 1f6758b8e0f7c8..07d635bbba0605 100644 --- a/packages/components/src/toggle-group-control/component.tsx +++ b/packages/components/src/toggle-group-control/component.tsx @@ -20,13 +20,13 @@ import { useContextSystem, WordPressComponentProps, } from '../ui/context'; +import { useUpdateEffect, useCx } from '../utils/hooks'; import { View } from '../view'; import BaseControl from '../base-control'; -import * as styles from './styles'; -import { useUpdateEffect, useCx } from '../utils/hooks'; -import Backdrop from './toggle-group-control-backdrop'; +import ToggleGroupControlBackdrop from './toggle-group-control-backdrop'; import type { ToggleGroupControlProps } from './types'; import ToggleGroupControlContext from './toggle-group-control-context'; +import * as styles from './styles'; const noop = () => {}; @@ -101,10 +101,11 @@ function ToggleGroupControl( ref={ useMergeRefs( [ containerRef, forwardedRef ] ) } > { resizeListener } - <Backdrop + <ToggleGroupControlBackdrop { ...radio } containerRef={ containerRef } containerWidth={ sizes.width } + isAdaptiveWidth={ isAdaptiveWidth } /> { children } </RadioGroup> diff --git a/packages/components/src/toggle-group-control/toggle-group-control-backdrop.tsx b/packages/components/src/toggle-group-control/toggle-group-control-backdrop.tsx index bd444d245bc9f8..bce2df10eff8f2 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control-backdrop.tsx +++ b/packages/components/src/toggle-group-control/toggle-group-control-backdrop.tsx @@ -12,6 +12,7 @@ import { BackdropView } from './styles'; function ToggleGroupControlBackdrop( { containerRef, containerWidth, + isAdaptiveWidth, state, }: ToggleGroupControlBackdropProps ) { const [ left, setLeft ] = useState( 0 ); @@ -43,7 +44,7 @@ function ToggleGroupControlBackdrop( { setCanAnimate( true ); } ); } - }, [ canAnimate, containerRef, containerWidth, state ] ); + }, [ canAnimate, containerRef, containerWidth, state, isAdaptiveWidth ] ); return ( <BackdropView diff --git a/packages/components/src/toggle-group-control/types.ts b/packages/components/src/toggle-group-control/types.ts index 81ee891b285af8..801297ba064bb8 100644 --- a/packages/components/src/toggle-group-control/types.ts +++ b/packages/components/src/toggle-group-control/types.ts @@ -88,5 +88,6 @@ export type ToggleGroupControlButtonProps = { export type ToggleGroupControlBackdropProps = { containerRef: MutableRefObject< HTMLElement | undefined >; containerWidth?: number | null; + isAdaptiveWidth?: boolean; state?: any; }; From 7dc480de856facc5089ed9718b08eaa770144cb6 Mon Sep 17 00:00:00 2001 From: Ben Dwyer <ben@scruffian.com> Date: Tue, 7 Sep 2021 10:59:04 +0100 Subject: [PATCH 186/214] [Block Library - Query Pagination Next/Previous]: Add an arrow attribute and sync next/previous block's arrow (#33656) * Query Pagination Next: Add an arrow attribute * change to a segmented control * update comment * Use CSS for RTL * formatting change * add arrow inside the link * sync next+previous icons * handle rtl for used arrows * update fixtures + polish * add small margin * update unit and control's label * use block context and handle the arrow in `QueryPagination` block * fix arrow css class * remove unneeded provider * fix fixtures Co-authored-by: ntsekouras <ntsekouras@outlook.com> --- lib/compat/wordpress-5.8/index.php | 36 ++++++++++++++ .../src/query-pagination-next/block.json | 2 +- .../src/query-pagination-next/edit.js | 38 +++++++++++---- .../src/query-pagination-next/index.php | 6 ++- .../src/query-pagination-previous/block.json | 2 +- .../src/query-pagination-previous/edit.js | 38 +++++++++++---- .../src/query-pagination-previous/index.php | 6 ++- .../src/query-pagination/block.json | 9 ++++ .../src/query-pagination/edit.js | 48 ++++++++++++++++++- .../query-pagination-arrow-controls.js | 44 +++++++++++++++++ .../src/query-pagination/style.scss | 18 +++++++ .../blocks/core__query-pagination.json | 4 +- 12 files changed, 228 insertions(+), 23 deletions(-) create mode 100644 packages/block-library/src/query-pagination/query-pagination-arrow-controls.js diff --git a/lib/compat/wordpress-5.8/index.php b/lib/compat/wordpress-5.8/index.php index 29a97fb20de819..35b0f11eeae1fa 100644 --- a/lib/compat/wordpress-5.8/index.php +++ b/lib/compat/wordpress-5.8/index.php @@ -164,3 +164,39 @@ function gutenberg_register_legacy_query_loop_block() { add_action( 'init', 'gutenberg_register_legacy_query_loop_block' ); } + +if ( ! function_exists( 'get_query_pagination_arrow' ) ) { + /** + * Helper function that returns the proper pagination arrow html for + * `QueryPaginationNext` and `QueryPaginationPrevious` blocks based + * on the provided `paginationArrow` from `QueryPagination` context. + * + * It's used in QueryPaginationNext and QueryPaginationPrevious blocks. + * + * @param WP_Block $block Block instance. + * @param boolean $is_next Flag for hanlding `next/previous` blocks. + * + * @return string|null Returns the constructed WP_Query arguments. + */ + function get_query_pagination_arrow( $block, $is_next ) { + $arrow_map = array( + 'none' => '', + 'arrow' => array( + 'next' => '→', + 'previous' => '←', + ), + 'chevron' => array( + 'next' => '»', + 'previous' => '«', + ), + ); + if ( ! empty( $block->context['paginationArrow'] ) && array_key_exists( $block->context['paginationArrow'], $arrow_map ) && ! empty( $arrow_map[ $block->context['paginationArrow'] ] ) ) { + $pagination_type = $is_next ? 'next' : 'previous'; + $arrow_attribute = $block->context['paginationArrow']; + $arrow = $arrow_map[ $block->context['paginationArrow'] ][ $pagination_type ]; + $arrow_classes = "wp-block-query-pagination-$pagination_type-arrow is-arrow-$arrow_attribute"; + return "<span class='$arrow_classes'>$arrow</span>"; + } + return null; + } +} diff --git a/packages/block-library/src/query-pagination-next/block.json b/packages/block-library/src/query-pagination-next/block.json index afc69d6e14a3bd..f7d48504132220 100644 --- a/packages/block-library/src/query-pagination-next/block.json +++ b/packages/block-library/src/query-pagination-next/block.json @@ -11,7 +11,7 @@ "type": "string" } }, - "usesContext": [ "queryId", "query" ], + "usesContext": [ "queryId", "query", "paginationArrow" ], "supports": { "reusable": false, "html": false, diff --git a/packages/block-library/src/query-pagination-next/edit.js b/packages/block-library/src/query-pagination-next/edit.js index 4e48c0bcecaa6b..d91f3d7e0ba303 100644 --- a/packages/block-library/src/query-pagination-next/edit.js +++ b/packages/block-library/src/query-pagination-next/edit.js @@ -4,19 +4,41 @@ import { __ } from '@wordpress/i18n'; import { useBlockProps, PlainText } from '@wordpress/block-editor'; +const arrowMap = { + none: '', + arrow: '→', + chevron: '»', +}; + export default function QueryPaginationNextEdit( { attributes: { label }, setAttributes, + context: { paginationArrow }, } ) { + const displayArrow = arrowMap[ paginationArrow ]; return ( - <PlainText - __experimentalVersion={ 2 } - tagName="a" - aria-label={ __( 'Next page link' ) } - placeholder={ __( 'Next Page' ) } - value={ label } - onChange={ ( newLabel ) => setAttributes( { label: newLabel } ) } + <a + href="#pagination-next-pseudo-link" + onClick={ ( event ) => event.preventDefault() } { ...useBlockProps() } - /> + > + <PlainText + __experimentalVersion={ 2 } + tagName="span" + aria-label={ __( 'Next page link' ) } + placeholder={ __( 'Next Page' ) } + value={ label } + onChange={ ( newLabel ) => + setAttributes( { label: newLabel } ) + } + /> + { displayArrow && ( + <span + className={ `wp-block-query-pagination-next-arrow is-arrow-${ paginationArrow }` } + > + { displayArrow } + </span> + ) } + </a> ); } diff --git a/packages/block-library/src/query-pagination-next/index.php b/packages/block-library/src/query-pagination-next/index.php index 7b0b4051f1aac2..d091e1c6bbc0fe 100644 --- a/packages/block-library/src/query-pagination-next/index.php +++ b/packages/block-library/src/query-pagination-next/index.php @@ -22,7 +22,11 @@ function render_block_core_query_pagination_next( $attributes, $content, $block $wrapper_attributes = get_block_wrapper_attributes(); $default_label = __( 'Next Page' ); $label = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? $attributes['label'] : $default_label; - $content = ''; + $pagination_arrow = get_query_pagination_arrow( $block, true ); + if ( $pagination_arrow ) { + $label .= $pagination_arrow; + } + $content = ''; // Check if the pagination is for Query that inherits the global context. if ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] ) { diff --git a/packages/block-library/src/query-pagination-previous/block.json b/packages/block-library/src/query-pagination-previous/block.json index 78a53867d0b7a5..c3a05cc202d30e 100644 --- a/packages/block-library/src/query-pagination-previous/block.json +++ b/packages/block-library/src/query-pagination-previous/block.json @@ -11,7 +11,7 @@ "type": "string" } }, - "usesContext": [ "queryId", "query" ], + "usesContext": [ "queryId", "query", "paginationArrow" ], "supports": { "reusable": false, "html": false, diff --git a/packages/block-library/src/query-pagination-previous/edit.js b/packages/block-library/src/query-pagination-previous/edit.js index 2c0bf33005de81..c695a453ce1e38 100644 --- a/packages/block-library/src/query-pagination-previous/edit.js +++ b/packages/block-library/src/query-pagination-previous/edit.js @@ -4,19 +4,41 @@ import { __ } from '@wordpress/i18n'; import { useBlockProps, PlainText } from '@wordpress/block-editor'; +const arrowMap = { + none: '', + arrow: '←', + chevron: '«', +}; + export default function QueryPaginationPreviousEdit( { attributes: { label }, setAttributes, + context: { paginationArrow }, } ) { + const displayArrow = arrowMap[ paginationArrow ]; return ( - <PlainText - __experimentalVersion={ 2 } - tagName="a" - aria-label={ __( 'Previous page link' ) } - placeholder={ __( 'Previous Page' ) } - value={ label } - onChange={ ( newLabel ) => setAttributes( { label: newLabel } ) } + <a + href="#pagination-previous-pseudo-link" + onClick={ ( event ) => event.preventDefault() } { ...useBlockProps() } - /> + > + { displayArrow && ( + <span + className={ `wp-block-query-pagination-previous-arrow is-arrow-${ paginationArrow }` } + > + { displayArrow } + </span> + ) } + <PlainText + __experimentalVersion={ 2 } + tagName="span" + aria-label={ __( 'Previous page link' ) } + placeholder={ __( 'Previous Page' ) } + value={ label } + onChange={ ( newLabel ) => + setAttributes( { label: newLabel } ) + } + /> + </a> ); } diff --git a/packages/block-library/src/query-pagination-previous/index.php b/packages/block-library/src/query-pagination-previous/index.php index ac319d0be4dbf5..47506496722d8b 100644 --- a/packages/block-library/src/query-pagination-previous/index.php +++ b/packages/block-library/src/query-pagination-previous/index.php @@ -21,7 +21,11 @@ function render_block_core_query_pagination_previous( $attributes, $content, $bl $wrapper_attributes = get_block_wrapper_attributes(); $default_label = __( 'Previous Page' ); $label = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? $attributes['label'] : $default_label; - $content = ''; + $pagination_arrow = get_query_pagination_arrow( $block, false ); + if ( $pagination_arrow ) { + $label = $pagination_arrow . $label; + } + $content = ''; // Check if the pagination is for Query that inherits the global context // and handle appropriately. if ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] ) { diff --git a/packages/block-library/src/query-pagination/block.json b/packages/block-library/src/query-pagination/block.json index c1de24977f37f6..45b7a42bffbadb 100644 --- a/packages/block-library/src/query-pagination/block.json +++ b/packages/block-library/src/query-pagination/block.json @@ -6,7 +6,16 @@ "parent": [ "core/query" ], "description": "Displays a paginated navigation to next/previous set of posts, when applicable.", "textdomain": "default", + "attributes": { + "paginationArrow": { + "type": "string", + "default": "none" + } + }, "usesContext": [ "queryId", "query" ], + "providesContext": { + "paginationArrow": "paginationArrow" + }, "supports": { "align": true, "reusable": false, diff --git a/packages/block-library/src/query-pagination/edit.js b/packages/block-library/src/query-pagination/edit.js index 47ac55f30693a7..c3f4f53f5f2b38 100644 --- a/packages/block-library/src/query-pagination/edit.js +++ b/packages/block-library/src/query-pagination/edit.js @@ -1,10 +1,20 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { + InspectorControls, useBlockProps, __experimentalUseInnerBlocksProps as useInnerBlocksProps, + store as blockEditorStore, } from '@wordpress/block-editor'; +import { useSelect } from '@wordpress/data'; +import { PanelBody } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { QueryPaginationArrowControls } from './query-pagination-arrow-controls'; const TEMPLATE = [ [ 'core/query-pagination-previous' ], @@ -12,7 +22,25 @@ const TEMPLATE = [ [ 'core/query-pagination-next' ], ]; -export default function QueryPaginationEdit() { +export default function QueryPaginationEdit( { + attributes: { paginationArrow }, + setAttributes, + clientId, +} ) { + const hasNextPreviousBlocks = useSelect( ( select ) => { + const { getBlocks } = select( blockEditorStore ); + const innerBlocks = getBlocks( clientId ); + /** + * Show the `paginationArrow` control only if a + * `QueryPaginationNext/Previous` block exists. + */ + return innerBlocks?.find( ( innerBlock ) => { + return [ + 'core/query-pagination-next', + 'core/query-pagination-previous', + ].includes( innerBlock.name ); + } ); + }, [] ); const blockProps = useBlockProps(); const innerBlocksProps = useInnerBlocksProps( blockProps, { template: TEMPLATE, @@ -23,5 +51,21 @@ export default function QueryPaginationEdit() { ], orientation: 'horizontal', } ); - return <div { ...innerBlocksProps } />; + return ( + <> + { hasNextPreviousBlocks && ( + <InspectorControls> + <PanelBody title={ __( 'Settings' ) }> + <QueryPaginationArrowControls + value={ paginationArrow } + onChange={ ( value ) => { + setAttributes( { paginationArrow: value } ); + } } + /> + </PanelBody> + </InspectorControls> + ) } + <div { ...innerBlocksProps } /> + </> + ); } diff --git a/packages/block-library/src/query-pagination/query-pagination-arrow-controls.js b/packages/block-library/src/query-pagination/query-pagination-arrow-controls.js new file mode 100644 index 00000000000000..28ae36ed415726 --- /dev/null +++ b/packages/block-library/src/query-pagination/query-pagination-arrow-controls.js @@ -0,0 +1,44 @@ +/** + * WordPress dependencies + */ +import { __, _x } from '@wordpress/i18n'; +import { + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, +} from '@wordpress/components'; + +export function QueryPaginationArrowControls( { value, onChange } ) { + return ( + <ToggleGroupControl + label={ __( 'Arrow' ) } + value={ value } + onChange={ onChange } + help={ __( + 'A decorative arrow appended to the next and previous page link.' + ) } + isBlock + > + <ToggleGroupControlOption + value="none" + label={ _x( + 'None', + 'Arrow option for Query Pagination Next/Previous blocks' + ) } + /> + <ToggleGroupControlOption + value="arrow" + label={ _x( + 'Arrow', + 'Arrow option for Query Pagination Next/Previous blocks' + ) } + /> + <ToggleGroupControlOption + value="chevron" + label={ _x( + 'Chevron', + 'Arrow option for Query Pagination Next/Previous blocks' + ) } + /> + </ToggleGroupControl> + ); +} diff --git a/packages/block-library/src/query-pagination/style.scss b/packages/block-library/src/query-pagination/style.scss index e140863a7d981a..5a4a950757cd7e 100644 --- a/packages/block-library/src/query-pagination/style.scss +++ b/packages/block-library/src/query-pagination/style.scss @@ -18,4 +18,22 @@ $pagination-margin: 0.5em; margin-right: 0; } } + .wp-block-query-pagination-previous-arrow { + margin-right: 1ch; + display: inline-block; // Needed so that the transform works. + // chevron(`»`) symbol doesn't need the mirroring by us. + &:not(.is-arrow-chevron) { + // Flip for RTL. + transform: scaleX(1) #{"/*rtl:scaleX(-1);*/"}; // This points the arrow right for LTR and left for RTL. + } + } + .wp-block-query-pagination-next-arrow { + margin-left: 1ch; + display: inline-block; // Needed so that the transform works. + // chevron(`»`) symbol doesn't need the mirroring by us. + &:not(.is-arrow-chevron) { + // Flip for RTL. + transform: scaleX(1) #{"/*rtl:scaleX(-1);*/"}; // This points the arrow right for LTR and left for RTL. + } + } } diff --git a/test/integration/fixtures/blocks/core__query-pagination.json b/test/integration/fixtures/blocks/core__query-pagination.json index a5ade7819c893b..9a5c156770fbf9 100644 --- a/test/integration/fixtures/blocks/core__query-pagination.json +++ b/test/integration/fixtures/blocks/core__query-pagination.json @@ -3,7 +3,9 @@ "clientId": "_clientId_0", "name": "core/query-pagination", "isValid": true, - "attributes": {}, + "attributes": { + "paginationArrow": "none" + }, "innerBlocks": [], "originalContent": "<div class=\"wp-block-query-pagination\"></div>" } From c00e28dda173fe457e9f706b049602ae7b51ced5 Mon Sep 17 00:00:00 2001 From: George Mamadashvili <georgemamadashvili@gmail.com> Date: Tue, 7 Sep 2021 10:00:42 +0000 Subject: [PATCH 187/214] Core Data: Adds 'include' to the query key (#34583) --- .../core-data/src/queried-data/get-query-parts.js | 15 +++++++++------ .../src/queried-data/test/get-query-parts.js | 2 +- .../core-data/src/queried-data/test/selectors.js | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/core-data/src/queried-data/get-query-parts.js b/packages/core-data/src/queried-data/get-query-parts.js index 1cd96314955053..9075adb71fabf5 100644 --- a/packages/core-data/src/queried-data/get-query-parts.js +++ b/packages/core-data/src/queried-data/get-query-parts.js @@ -60,12 +60,6 @@ export function getQueryParts( query ) { parts.perPage = Number( value ); break; - case 'include': - parts.include = getNormalizedCommaSeparable( value ).map( - Number - ); - break; - case 'context': parts.context = value; break; @@ -82,6 +76,15 @@ export function getQueryParts( query ) { value = parts.fields.join(); } + // Two requests with different include values cannot have same results. + if ( key === 'include' ) { + parts.include = getNormalizedCommaSeparable( value ).map( + Number + ); + // Normalize value for `stableKey`. + value = parts.include.join(); + } + // While it could be any deterministic string, for simplicity's // sake mimic querystring encoding for stable key. // diff --git a/packages/core-data/src/queried-data/test/get-query-parts.js b/packages/core-data/src/queried-data/test/get-query-parts.js index 0de6036d696b30..b3ca692ad09f50 100644 --- a/packages/core-data/src/queried-data/test/get-query-parts.js +++ b/packages/core-data/src/queried-data/test/get-query-parts.js @@ -24,7 +24,7 @@ describe( 'getQueryParts', () => { context: 'default', page: 1, perPage: 10, - stableKey: '', + stableKey: 'include=1', fields: null, include: [ 1 ], } ); diff --git a/packages/core-data/src/queried-data/test/selectors.js b/packages/core-data/src/queried-data/test/selectors.js index 39b46a97a9395e..f0a38aab2887e1 100644 --- a/packages/core-data/src/queried-data/test/selectors.js +++ b/packages/core-data/src/queried-data/test/selectors.js @@ -82,6 +82,7 @@ describe( 'getQueriedItems', () => { queries: { default: { '': [ 1, 2 ], + 'include=1': [ 1 ], }, }, }; From eab8c633b7df6d1651e998e75d07a16fd2459fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Ruiti=C3=B1a?= <juanruitina@users.noreply.github.com> Date: Tue, 7 Sep 2021 12:08:02 +0200 Subject: [PATCH 188/214] Keep id on paste if internal link points to it (#31107) * Keep id on paste if internal link points to it If link has obsolete name attribute, save as id and remove name Remove links with no id nor href attributes (+ test) Update raw handling readme: footnote support, LibreOffice * Remove unnecessary code No need to remove name attribute, handled by removeInvalidHTML * Add Word paste test for internal links --- .../raw-handling/phrasing-content-reducer.js | 13 ++++ .../blocks/src/api/raw-handling/readme.md | 32 ++++---- packages/dom/src/phrasing-content.js | 2 +- .../fixtures/documents/ms-word-in.html | 76 +++++++++++++++++++ .../fixtures/documents/ms-word-out.html | 40 ++++++++++ 5 files changed, 147 insertions(+), 16 deletions(-) diff --git a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js index cd1e45d077e4d3..d1e3a3e43439ac 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js @@ -56,5 +56,18 @@ export default function phrasingContentReducer( node, doc ) { node.removeAttribute( 'target' ); node.removeAttribute( 'rel' ); } + + // Saves anchor elements name attribute as id + if ( node.name && ! node.id ) { + node.id = node.name; + } + + // Keeps id only if there is an internal link pointing to it + if ( + node.id && + ! node.ownerDocument.querySelector( `[href="#${ node.id }"]` ) + ) { + node.removeAttribute( 'id' ); + } } } diff --git a/packages/blocks/src/api/raw-handling/readme.md b/packages/blocks/src/api/raw-handling/readme.md index eb484450758c87..6c2be6929a597e 100644 --- a/packages/blocks/src/api/raw-handling/readme.md +++ b/packages/blocks/src/api/raw-handling/readme.md @@ -4,22 +4,24 @@ This folder contains all paste specific logic (filters, converters, normalisers. ## Support table -| Source | Formatting | Headings | Lists | Image | Separator | Table | -| ---------------- | ---------- | -------- | ----- | ----- | --------- | ----- | -| Google Docs | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Apple Pages | ✓ | ✘ [1] | ✓ | ✘ [1] | n/a | ✓ | -| MS Word | ✓ | ✓ | ✓ | ✘ [2] | n/a | ✓ | -| MS Word Online | ✓ | ✘ [3] | ✓ | ✓ | n/a | ✓ | -| Evernote | ✓ | ✘ [4] | ✓ | ✓ | ✓ | ✓ | -| Markdown | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Legacy WordPress | ✓ | ✓ | ✓ | … [5] | ✓ | ✓ | -| Web | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Source | Formatting | Headings | Lists | Image | Separator | Table | Footnotes, endnotes | +| ---------------- | ---------- | -------- | ----- | ----- | --------- | ----- | ------------------- | +| Google Docs | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ [1] | +| Apple Pages | ✓ | ✘ [2] | ✓ | ✘ [2] | n/a | ✓ | ✘ [1] | +| MS Word | ✓ | ✓ | ✓ | ✘ [3] | n/a | ✓ | ✓ | +| MS Word Online | ✓ | ✘ [4] | ✓ | ✓ | n/a | ✓ | ✘ [1] | +| LibreOffice | ✓ | ✓ | ✓ | ✘ [3] | ✓ | ✓ | ✓ | +| Evernote | ✓ | ✘ [5] | ✓ | ✓ | ✓ | ✓ | n/a | +| Markdown | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | n/a | +| Legacy WordPress | ✓ | ✓ | ✓ | … [6] | ✓ | ✓ | n/a | +| Web | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | n/a | -1. Apple Pages does not pass heading and image information. -2. MS Word only provides a local file path, which cannot be accessed in JavaScript for security reasons. Image placeholders will be provided instead. Single images, however, _can_ be copied and pasted without any problem. -3. Still to do for MS Word Online. -4. Evernote does not have headings. -5. For caption and gallery shortcodes, see #2874. +1. Google Docs, Apple Pages and MS Word online don't pass footnote nor endnote information. +2. Apple Pages does not pass heading and image information. +3. MS Word and LibreOffice only provide a local file path, which cannot be accessed in JavaScript for security reasons. Image placeholders will be provided instead. Single images, however, _can_ be copied and pasted without any problem. +4. Still to do for MS Word Online. +5. Evernote does not have headings. +6. For caption and gallery shortcodes, see #2874. ## Other notable capabilities diff --git a/packages/dom/src/phrasing-content.js b/packages/dom/src/phrasing-content.js index d233ef2eec8b4a..494b7026f2b3cb 100644 --- a/packages/dom/src/phrasing-content.js +++ b/packages/dom/src/phrasing-content.js @@ -32,7 +32,7 @@ const textContentSchema = { s: {}, del: {}, ins: {}, - a: { attributes: [ 'href', 'target', 'rel' ] }, + a: { attributes: [ 'href', 'target', 'rel', 'id' ] }, code: {}, abbr: { attributes: [ 'title' ] }, sub: {}, diff --git a/test/integration/fixtures/documents/ms-word-in.html b/test/integration/fixtures/documents/ms-word-in.html index d65299287e642e..2a13d028a5d24e 100644 --- a/test/integration/fixtures/documents/ms-word-in.html +++ b/test/integration/fixtures/documents/ms-word-in.html @@ -201,3 +201,79 @@ <h2><span lang=EN-US style='mso-ansi-language:EN-US'>This is a heading level 2<o src="file:LOW-RES.png" v:shapes="Picture_x0020_1"><![endif]></span><span lang=EN-US style='mso-ansi-language: EN-US'><o:p></o:p></span></p> + +<p class=MsoNormal><a href="#anchor"><span lang=EN-US style='mso-ansi-language: +EN-US'>This is an anchor link</span></a><span lang=EN-US style='mso-ansi-language: +EN-US'> that leads to the next paragraph.<o:p></o:p></span></p> + +<p class=MsoNormal><a name=anchor><span lang=EN-US style='mso-ansi-language: +EN-US'>This is the paragraph with the anchor.<o:p></o:p></span></a></p> + +<span style='mso-bookmark:anchor'></span> + +<p class=MsoNormal><a href="#nowhere"><span lang=EN-US style='mso-ansi-language: +EN-US'>This is an anchor link</span></a><span lang=EN-US style='mso-ansi-language: +EN-US'> that leads nowhere.<o:p></o:p></span></p> + +<p class=MsoNormal><a name=anchor2><span lang=EN-US style='mso-ansi-language: +EN-US'>This is a paragraph with an anchor with no link pointing to it.<o:p></o:p></span></a></p> + +<span style='mso-bookmark:anchor2'></span> + +<p class=MsoNormal><span lang=EN-US style='mso-ansi-language:EN-US'>This is a +reference to a footnote</span><a style='mso-footnote-id:ftn1' href="#_ftn1" +name="_ftnref1" title=""><span class=MsoFootnoteReference><span +style='mso-special-character:footnote'><![if !supportFootnotes]><span +class=MsoFootnoteReference><span style='font-size:11.0pt;line-height:105%; +font-family:"Calibri",sans-serif;mso-ascii-theme-font:minor-latin; +mso-ansi-language:EN-US'>[1]</span></span><![endif]></span></span></a><span +lang=EN-US style='mso-ansi-language:EN-US'>.<o:p></o:p></span></p> + +<p class=MsoNormal><span lang=EN-US style='mso-ansi-language:EN-US'>This is a +reference to an endnote</span><a style='mso-endnote-id:edn1' href="#_edn1" +name="_ednref1" title=""><span class=MsoEndnoteReference><span +style='mso-special-character:footnote'><![if !supportFootnotes]><span +class=MsoEndnoteReference><span style='font-size:11.0pt;line-height:105%; +font-family:"Calibri",sans-serif;mso-ascii-theme-font:minor-latin; +mso-ansi-language:EN-US'>[i]</span></span><![endif]></span></span></a><span +lang=EN-US style='mso-ansi-language:EN-US'>.<o:p></o:p></span></p> + +<div style='mso-element:footnote-list'><![if !supportFootnotes]><br clear=all> + +<hr align=left size=1 width="33%"> + +<![endif]> + +<div style='mso-element:footnote' id=ftn1> + +<p class=MsoFootnoteText><a style='mso-footnote-id:ftn1' href="#_ftnref1" +name="_ftn1" title=""><span class=MsoFootnoteReference><span style='mso-special-character: +footnote'><![if !supportFootnotes]><span class=MsoFootnoteReference><span +style='font-size:10.0pt;line-height:105%;font-family:"Calibri",sans-serif; +mso-bidi-theme-font:minor-latin; +mso-ansi-language:EN-US'>[1]</span></span><![endif]></span></span></a> This is +a footnote.</p> + +</div> + +</div> + +<div style='mso-element:endnote-list'><![if !supportEndnotes]><br clear=all> + +<hr align=left size=1 width="33%"> + +<![endif]> + +<div style='mso-element:endnote' id=edn1> + +<p class=MsoEndnoteText><a style='mso-endnote-id:edn1' href="#_ednref1" +name="_edn1" title=""><span class=MsoEndnoteReference><span style='mso-special-character: +footnote'><![if !supportFootnotes]><span class=MsoEndnoteReference><span +style='font-size:10.0pt;line-height:105%;font-family:"Calibri",sans-serif; +mso-bidi-theme-font:minor-latin; +mso-ansi-language:EN-US'>[i]</span></span><![endif]></span></span></a> This is +an endnote.</p> + +</div> + +</div> diff --git a/test/integration/fixtures/documents/ms-word-out.html b/test/integration/fixtures/documents/ms-word-out.html index fba58fd4fb9dda..0b1f49fbd692c0 100644 --- a/test/integration/fixtures/documents/ms-word-out.html +++ b/test/integration/fixtures/documents/ms-word-out.html @@ -37,3 +37,43 @@ <h2>This is a heading level 2</h2> <!-- wp:image --> <figure class="wp-block-image"><img src="" alt=""/></figure> <!-- /wp:image --> + +<!-- wp:paragraph --> +<p><a href="#anchor">This is an anchor link</a> that leads to the next paragraph.</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a id="anchor">This is the paragraph with the anchor.</a></p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="#nowhere">This is an anchor link</a> that leads nowhere.</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a>This is a paragraph with an anchor with no link pointing to it.</a></p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>This is a reference to a footnote<a href="#_ftn1" id="_ftnref1">[1]</a>.</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>This is a reference to an endnote<a href="#_edn1" id="_ednref1">[i]</a>.</p> +<!-- /wp:paragraph --> + +<!-- wp:separator --> +<hr class="wp-block-separator"/> +<!-- /wp:separator --> + +<!-- wp:paragraph --> +<p><a href="#_ftnref1" id="_ftn1">[1]</a> This is a footnote.</p> +<!-- /wp:paragraph --> + +<!-- wp:separator --> +<hr class="wp-block-separator"/> +<!-- /wp:separator --> + +<!-- wp:paragraph --> +<p><a href="#_ednref1" id="_edn1">[i]</a> This is an endnote.</p> +<!-- /wp:paragraph --> From 1f80940b22a7d69fee06a6de2882969e5d90462d Mon Sep 17 00:00:00 2001 From: skierpage <info@skierpage.com> Date: Tue, 7 Sep 2021 03:14:56 -0700 Subject: [PATCH 189/214] MediaPlaceholder: Change media URL input type to allow a local URL path (#29138) --- packages/block-editor/src/components/media-placeholder/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index f00eae7650e487..1fb11cd0aa0897 100644 --- a/packages/block-editor/src/components/media-placeholder/index.js +++ b/packages/block-editor/src/components/media-placeholder/index.js @@ -36,7 +36,7 @@ const InsertFromURLPopover = ( { src, onChange, onSubmit, onClose } ) => ( > <input className="block-editor-media-placeholder__url-input-field" - type="url" + type="text" aria-label={ __( 'URL' ) } placeholder={ __( 'Paste or type URL' ) } onChange={ onChange } From d98b791567fb6213cf86a3a6969674e2fbd8d867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 7 Sep 2021 12:53:42 +0200 Subject: [PATCH 190/214] Show wide control conditionally (#34586) --- packages/block-editor/src/layouts/flow.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/layouts/flow.js b/packages/block-editor/src/layouts/flow.js index 3e0fc374ee5483..1824b5120e84fc 100644 --- a/packages/block-editor/src/layouts/flow.js +++ b/packages/block-editor/src/layouts/flow.js @@ -160,8 +160,16 @@ export default { return layout.alignments; } - return layout.contentSize || layout.wideSize - ? [ 'wide', 'full', 'left', 'center', 'right' ] - : [ 'left', 'center', 'right' ]; + const alignments = [ 'left', 'center', 'right' ]; + + if ( layout.contentSize ) { + alignments.unshift( 'full' ); + } + + if ( layout.wideSize ) { + alignments.unshift( 'wide' ); + } + + return alignments; }, }; From 239babc2825bba33f535f5e1d0f809b4e5c20606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 7 Sep 2021 13:01:26 +0200 Subject: [PATCH 191/214] Allow disabling `text` and `background` color via `theme.json` (#34420) --- docs/how-to-guides/themes/theme-json.md | 4 +- lib/class-wp-theme-json-gutenberg.php | 2 + lib/theme.json | 4 +- packages/block-editor/src/hooks/color.js | 4 ++ .../src/components/sidebar/color-panel.js | 48 +++++++++++++------ 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md index b4151d83071f18..6335a614f9b3cb 100644 --- a/docs/how-to-guides/themes/theme-json.md +++ b/docs/how-to-guides/themes/theme-json.md @@ -222,13 +222,15 @@ The settings section has the following structure: "customWidth": false }, "color": { + "background": true, "custom": true, "customDuotone": true, "customGradient": true, "duotone": [], "gradients": [], "link": false, - "palette": [] + "palette": [], + "text": true }, "custom": {}, "layout": { diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index b3f2cbe6fd2c34..29cca882ffa138 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -85,6 +85,7 @@ class WP_Theme_JSON_Gutenberg { 'customWidth' => null, ), 'color' => array( + 'background' => null, 'custom' => null, 'customDuotone' => null, 'customGradient' => null, @@ -92,6 +93,7 @@ class WP_Theme_JSON_Gutenberg { 'gradients' => null, 'link' => null, 'palette' => null, + 'text' => null, ), 'custom' => null, 'layout' => array( diff --git a/lib/theme.json b/lib/theme.json index 37ccaf0c010df8..f32734b6efe0f4 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -2,6 +2,7 @@ "version": 1, "settings": { "color": { + "background": true, "palette": [ { "name": "Black", @@ -171,7 +172,8 @@ "custom": true, "customDuotone": true, "customGradient": true, - "link": false + "link": false, + "text": true }, "typography": { "dropCap": true, diff --git a/packages/block-editor/src/hooks/color.js b/packages/block-editor/src/hooks/color.js index d89e91dae87ddb..fa8fb9e0cbdd22 100644 --- a/packages/block-editor/src/hooks/color.js +++ b/packages/block-editor/src/hooks/color.js @@ -222,6 +222,8 @@ export function ColorEdit( props ) { const areCustomSolidsEnabled = useSetting( 'color.custom' ); const areCustomGradientsEnabled = useSetting( 'color.customGradient' ); const isLinkEnabled = useSetting( 'color.link' ); + const isTextEnabled = useSetting( 'color.text' ); + const isBackgroundEnabled = useSetting( 'color.background' ); // Shouldn't be needed but right now the ColorGradientsPanel // can trigger both onChangeColor and onChangeBackground @@ -242,9 +244,11 @@ export function ColorEdit( props ) { ( solids.length > 0 || areCustomSolidsEnabled ); const hasTextColor = hasTextColorSupport( blockName ) && + isTextEnabled && ( solids.length > 0 || areCustomSolidsEnabled ); const hasBackgroundColor = hasBackgroundColorSupport( blockName ) && + isBackgroundEnabled && ( solids.length > 0 || areCustomSolidsEnabled ); const hasGradientColor = hasGradientSupport( blockName ) && diff --git a/packages/edit-site/src/components/sidebar/color-panel.js b/packages/edit-site/src/components/sidebar/color-panel.js index 5dfdc2268f570d..ddd01fbc0789f6 100644 --- a/packages/edit-site/src/components/sidebar/color-panel.js +++ b/packages/edit-site/src/components/sidebar/color-panel.js @@ -27,14 +27,36 @@ export default function ColorPanel( { getSetting, setSetting, } ) { - const colors = useSetting( 'color.palette', name ); - const disableCustomColors = ! useSetting( 'color.custom', name ); + const solids = useSetting( 'color.palette', name ); const gradients = useSetting( 'color.gradients', name ); - const disableCustomGradients = ! useSetting( 'color.customGradient', name ); + const areCustomSolidsEnabled = useSetting( 'color.custom', name ); + const areCustomGradientsEnabled = useSetting( + 'color.customGradient', + name + ); + const isLinkEnabled = useSetting( 'color.link', name ); + const isTextEnabled = useSetting( 'color.text', name ); + const isBackgroundEnabled = useSetting( 'color.background', name ); + + const hasLinkColor = + supports.includes( 'linkColor' ) && + isLinkEnabled && + ( solids.length > 0 || areCustomSolidsEnabled ); + const hasTextColor = + supports.includes( 'color' ) && + isTextEnabled && + ( solids.length > 0 || areCustomSolidsEnabled ); + const hasBackgroundColor = + supports.includes( 'backgroundColor' ) && + isBackgroundEnabled && + ( solids.length > 0 || areCustomSolidsEnabled ); + const hasGradientColor = + supports.includes( 'background' ) && + ( gradients.length > 0 || areCustomGradientsEnabled ); const settings = []; - if ( supports.includes( 'color' ) ) { + if ( hasTextColor ) { const color = getStyle( name, 'color' ); const userColor = getStyle( name, 'color', 'user' ); settings.push( { @@ -46,7 +68,7 @@ export default function ColorPanel( { } let backgroundSettings = {}; - if ( supports.includes( 'backgroundColor' ) ) { + if ( hasBackgroundColor ) { const backgroundColor = getStyle( name, 'backgroundColor' ); const userBackgroundColor = getStyle( name, 'backgroundColor', 'user' ); backgroundSettings = { @@ -61,7 +83,7 @@ export default function ColorPanel( { } let gradientSettings = {}; - if ( supports.includes( 'background' ) ) { + if ( hasGradientColor ) { const gradient = getStyle( name, 'background' ); const userGradient = getStyle( name, 'background', 'user' ); gradientSettings = { @@ -74,10 +96,7 @@ export default function ColorPanel( { } } - if ( - supports.includes( 'background' ) || - supports.includes( 'backgroundColor' ) - ) { + if ( hasBackgroundColor || hasGradientColor ) { settings.push( { ...backgroundSettings, ...gradientSettings, @@ -85,7 +104,7 @@ export default function ColorPanel( { } ); } - if ( supports.includes( 'linkColor' ) ) { + if ( hasLinkColor ) { const color = getStyle( name, 'linkColor' ); const userColor = getStyle( name, 'linkColor', 'user' ); settings.push( { @@ -95,14 +114,15 @@ export default function ColorPanel( { clearable: color === userColor, } ); } + return ( <PanelColorGradientSettings title={ __( 'Color' ) } settings={ settings } - colors={ colors } + colors={ solids } gradients={ gradients } - disableCustomColors={ disableCustomColors } - disableCustomGradients={ disableCustomGradients } + disableCustomColors={ ! areCustomSolidsEnabled } + disableCustomGradients={ ! areCustomGradientsEnabled } > <ColorPalettePanel key={ 'color-palette-panel-' + name } From a96a5d9814d309990e1340e57abea127477f6d99 Mon Sep 17 00:00:00 2001 From: Adam Zielinski <adam@adamziel.com> Date: Tue, 7 Sep 2021 13:07:47 +0200 Subject: [PATCH 192/214] Migrate canUser resolver to thunks (#34580) * Migrate canUser * Lint --- packages/core-data/src/resolvers.js | 19 +-- packages/core-data/src/test/resolvers.js | 152 +++++++++++------------ 2 files changed, 82 insertions(+), 89 deletions(-) diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 0e6ec93bd83dd6..1c25a502039454 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -27,7 +27,6 @@ import { receiveEntityRecords, receiveThemeSupports, receiveEmbedPreview, - receiveUserPermission, } from './actions'; import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; import { ifNotResolved, getNormalizedCommaSeparable } from './utils'; @@ -291,7 +290,7 @@ export function* getEmbedPreview( url ) { * @param {string} resource REST resource to check, e.g. 'media' or 'posts'. * @param {?string} id ID of the rest resource to check. */ -export function* canUser( action, resource, id ) { +export const canUser = ( action, resource, id ) => async ( { dispatch } ) => { const methods = { create: 'POST', read: 'GET', @@ -308,7 +307,7 @@ export function* canUser( action, resource, id ) { let response; try { - response = yield apiFetch( { + response = await triggerFetch( { path, // Ideally this would always be an OPTIONS request, but unfortunately there's // a bug in the REST API which causes the Allow header to not be sent on @@ -336,8 +335,8 @@ export function* canUser( action, resource, id ) { const key = compact( [ action, resource, id ] ).join( '/' ); const isAllowed = includes( allowHeader, method ); - yield receiveUserPermission( key, isAllowed ); -} + dispatch.receiveUserPermission( key, isAllowed ); +}; /** * Checks whether the current user can perform the given action on the given @@ -347,16 +346,18 @@ export function* canUser( action, resource, id ) { * @param {string} name Entity name. * @param {string} recordId Record's id. */ -export function* canUserEditEntityRecord( kind, name, recordId ) { - const entities = yield getKindEntities( kind ); +export const canUserEditEntityRecord = ( kind, name, recordId ) => async ( { + dispatch, +} ) => { + const entities = await dispatch( getKindEntities( kind ) ); const entity = find( entities, { kind, name } ); if ( ! entity ) { return; } const resource = entity.__unstable_rest_base; - yield canUser( 'update', resource, recordId ); -} + await dispatch( canUser( 'update', resource, recordId ) ); +}; /** * Request autosave data from the REST API. diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index 91d28f6ab5ea42..1893d7a27bb97d 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { apiFetch } from '@wordpress/data-controls'; import triggerFetch from '@wordpress/api-fetch'; jest.mock( '@wordpress/api-fetch' ); @@ -17,11 +16,7 @@ import { getAutosaves, getCurrentUser, } from '../resolvers'; -import { - receiveEmbedPreview, - receiveUserPermission, - receiveCurrentUser, -} from '../actions'; +import { receiveEmbedPreview, receiveCurrentUser } from '../actions'; describe( 'getEntityRecord', () => { const POST_TYPE = { slug: 'post' }; @@ -279,106 +274,103 @@ describe( 'getEmbedPreview', () => { } ); describe( 'canUser', () => { - it( 'does nothing when there is an API error', () => { - const generator = canUser( 'create', 'media' ); - - let received = generator.next(); - expect( received.done ).toBe( false ); - expect( received.value ).toEqual( - apiFetch( { - path: '/wp/v2/media', - method: 'OPTIONS', - parse: false, - } ) + beforeEach( async () => { + triggerFetch.mockReset(); + } ); + + it( 'does nothing when there is an API error', async () => { + const dispatch = Object.assign( jest.fn(), { + receiveUserPermission: jest.fn(), + } ); + + triggerFetch.mockImplementation( () => + Promise.reject( { status: 404 } ) ); - received = generator.throw( { status: 404 } ); - expect( received.done ).toBe( true ); - expect( received.value ).toBeUndefined(); + await canUser( 'create', 'media' )( { dispatch } ); + + expect( triggerFetch ).toHaveBeenCalledWith( { + path: '/wp/v2/media', + method: 'OPTIONS', + parse: false, + } ); + + expect( dispatch.receiveUserPermission ).not.toHaveBeenCalled(); } ); - it( 'receives false when the user is not allowed to perform an action', () => { - const generator = canUser( 'create', 'media' ); - - let received = generator.next(); - expect( received.done ).toBe( false ); - expect( received.value ).toEqual( - apiFetch( { - path: '/wp/v2/media', - method: 'OPTIONS', - parse: false, - } ) - ); + it( 'receives false when the user is not allowed to perform an action', async () => { + const dispatch = Object.assign( jest.fn(), { + receiveUserPermission: jest.fn(), + } ); - received = generator.next( { + triggerFetch.mockImplementation( () => ( { headers: { Allow: 'GET', }, + } ) ); + + await canUser( 'create', 'media' )( { dispatch } ); + + expect( triggerFetch ).toHaveBeenCalledWith( { + path: '/wp/v2/media', + method: 'OPTIONS', + parse: false, } ); - expect( received.done ).toBe( false ); - expect( received.value ).toEqual( - receiveUserPermission( 'create/media', false ) - ); - received = generator.next(); - expect( received.done ).toBe( true ); - expect( received.value ).toBeUndefined(); + expect( dispatch.receiveUserPermission ).toHaveBeenCalledWith( + 'create/media', + false + ); } ); - it( 'receives true when the user is allowed to perform an action', () => { - const generator = canUser( 'create', 'media' ); - - let received = generator.next(); - expect( received.done ).toBe( false ); - expect( received.value ).toEqual( - apiFetch( { - path: '/wp/v2/media', - method: 'OPTIONS', - parse: false, - } ) - ); + it( 'receives true when the user is allowed to perform an action', async () => { + const dispatch = Object.assign( jest.fn(), { + receiveUserPermission: jest.fn(), + } ); - received = generator.next( { + triggerFetch.mockImplementation( () => ( { headers: { Allow: 'POST, GET, PUT, DELETE', }, + } ) ); + + await canUser( 'create', 'media' )( { dispatch } ); + + expect( triggerFetch ).toHaveBeenCalledWith( { + path: '/wp/v2/media', + method: 'OPTIONS', + parse: false, } ); - expect( received.done ).toBe( false ); - expect( received.value ).toEqual( - receiveUserPermission( 'create/media', true ) - ); - received = generator.next(); - expect( received.done ).toBe( true ); - expect( received.value ).toBeUndefined(); + expect( dispatch.receiveUserPermission ).toHaveBeenCalledWith( + 'create/media', + true + ); } ); - it( 'receives true when the user is allowed to perform an action on a specific resource', () => { - const generator = canUser( 'update', 'blocks', 123 ); - - let received = generator.next(); - expect( received.done ).toBe( false ); - expect( received.value ).toEqual( - apiFetch( { - path: '/wp/v2/blocks/123', - method: 'GET', - parse: false, - } ) - ); + it( 'receives true when the user is allowed to perform an action on a specific resource', async () => { + const dispatch = Object.assign( jest.fn(), { + receiveUserPermission: jest.fn(), + } ); - received = generator.next( { + triggerFetch.mockImplementation( () => ( { headers: { Allow: 'POST, GET, PUT, DELETE', }, + } ) ); + + await canUser( 'create', 'blocks', 123 )( { dispatch } ); + + expect( triggerFetch ).toHaveBeenCalledWith( { + path: '/wp/v2/blocks/123', + method: 'GET', + parse: false, } ); - expect( received.done ).toBe( false ); - expect( received.value ).toEqual( - receiveUserPermission( 'update/blocks/123', true ) - ); - received = generator.next(); - expect( received.done ).toBe( true ); - expect( received.value ).toBeUndefined(); + expect( dispatch.receiveUserPermission ).toHaveBeenCalledWith( + 'create/blocks/123', + true + ); } ); } ); From 7791d58cf790f275a754e21bdeb19109a8129682 Mon Sep 17 00:00:00 2001 From: Adam Zielinski <adam@adamziel.com> Date: Tue, 7 Sep 2021 15:08:37 +0200 Subject: [PATCH 193/214] Migrate getAuthors,_getCurrentUser,__getCurrentTheme,__getThemeSupports to thunks (#34579) --- packages/core-data/src/resolvers.js | 76 ++++++++++-------------- packages/core-data/src/test/resolvers.js | 57 +++++++++++------- 2 files changed, 66 insertions(+), 67 deletions(-) diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 1c25a502039454..0b02d83b064a54 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -7,27 +7,16 @@ import { find, includes, get, hasIn, compact, uniq } from 'lodash'; * WordPress dependencies */ import { addQueryArgs } from '@wordpress/url'; -import { controls } from '@wordpress/data'; -import { apiFetch } from '@wordpress/data-controls'; import triggerFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ -import { regularFetch } from './controls'; import { STORE_NAME } from './name'; /** * Internal dependencies */ -import { - receiveUserQuery, - receiveCurrentTheme, - receiveCurrentUser, - receiveEntityRecords, - receiveThemeSupports, - receiveEmbedPreview, -} from './actions'; import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; import { ifNotResolved, getNormalizedCommaSeparable } from './utils'; @@ -37,22 +26,22 @@ import { ifNotResolved, getNormalizedCommaSeparable } from './utils'; * @param {Object|undefined} query Optional object of query parameters to * include with request. */ -export function* getAuthors( query ) { +export const getAuthors = ( query ) => async ( { dispatch } ) => { const path = addQueryArgs( '/wp/v2/users/?who=authors&per_page=100', query ); - const users = yield apiFetch( { path } ); - yield receiveUserQuery( path, users ); -} + const users = await triggerFetch( { path } ); + dispatch.receiveUserQuery( path, users ); +}; /** * Requests the current user from the REST API. */ -export function* getCurrentUser() { - const currentUser = yield apiFetch( { path: '/wp/v2/users/me' } ); - yield receiveCurrentUser( currentUser ); -} +export const getCurrentUser = () => async ( { dispatch } ) => { + const currentUser = await triggerFetch( { path: '/wp/v2/users/me' } ); + dispatch.receiveCurrentUser( currentUser ); +}; /** * Requests an entity's record from the REST API. @@ -247,39 +236,39 @@ getEntityRecords.shouldInvalidate = ( action, kind, name ) => { /** * Requests the current theme. */ -export function* getCurrentTheme() { - const activeThemes = yield apiFetch( { +export const getCurrentTheme = () => async ( { dispatch } ) => { + const activeThemes = await triggerFetch( { path: '/wp/v2/themes?status=active', } ); - yield receiveCurrentTheme( activeThemes[ 0 ] ); -} + dispatch.receiveCurrentTheme( activeThemes[ 0 ] ); +}; /** * Requests theme supports data from the index. */ -export function* getThemeSupports() { - const activeThemes = yield apiFetch( { +export const getThemeSupports = () => async ( { dispatch } ) => { + const activeThemes = await triggerFetch( { path: '/wp/v2/themes?status=active', } ); - yield receiveThemeSupports( activeThemes[ 0 ].theme_supports ); -} + dispatch.receiveThemeSupports( activeThemes[ 0 ].theme_supports ); +}; /** * Requests a preview from the from the Embed API. * * @param {string} url URL to get the preview for. */ -export function* getEmbedPreview( url ) { +export const getEmbedPreview = ( url ) => async ( { dispatch } ) => { try { - const embedProxyResponse = yield apiFetch( { + const embedProxyResponse = await triggerFetch( { path: addQueryArgs( '/oembed/1.0/proxy', { url } ), } ); - yield receiveEmbedPreview( url, embedProxyResponse ); + dispatch.receiveEmbedPreview( url, embedProxyResponse ); } catch ( error ) { // Embed API 404s if the URL cannot be embedded, so we have to catch the error from the apiRequest here. - yield receiveEmbedPreview( url, false ); + dispatch.receiveEmbedPreview( url, false ); } -} +}; /** * Checks whether the current user can perform the given action on the given @@ -399,17 +388,19 @@ export const getAutosave = ( postType, postId ) => async ( { * * @param {string} link Link. */ -export function* __experimentalGetTemplateForLink( link ) { +export const __experimentalGetTemplateForLink = ( link ) => async ( { + dispatch, + resolveSelect, +} ) => { // Ideally this should be using an apiFetch call // We could potentially do so by adding a "filter" to the `wp_template` end point. // Also it seems the returned object is not a regular REST API post type. let template; try { - template = yield regularFetch( - addQueryArgs( link, { - '_wp-find-template': true, - } ) - ); + template = await window + .fetch( addQueryArgs( link, { '_wp-find-template': true } ) ) + .then( ( res ) => res.json() ) + .then( ( { data } ) => data ); } catch ( e ) { // For non-FSE themes, it is possible that this request returns an error. } @@ -418,21 +409,18 @@ export function* __experimentalGetTemplateForLink( link ) { return; } - yield getEntityRecord( 'postType', 'wp_template', template.id ); - const record = yield controls.select( - STORE_NAME, - 'getEntityRecord', + const record = await resolveSelect.getEntityRecord( 'postType', 'wp_template', template.id ); if ( record ) { - yield receiveEntityRecords( 'postType', 'wp_template', [ record ], { + dispatch.receiveEntityRecords( 'postType', 'wp_template', [ record ], { 'find-template': link, } ); } -} +}; __experimentalGetTemplateForLink.shouldInvalidate = ( action ) => { return ( diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index 1893d7a27bb97d..f5be29cb1543db 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -16,7 +16,6 @@ import { getAutosaves, getCurrentUser, } from '../resolvers'; -import { receiveEmbedPreview, receiveCurrentUser } from '../actions'; describe( 'getEntityRecord', () => { const POST_TYPE = { slug: 'post' }; @@ -250,25 +249,34 @@ describe( 'getEmbedPreview', () => { const UNEMBEDDABLE_URL = 'http://example.com/'; it( 'yields with fetched embed preview', async () => { - const fulfillment = getEmbedPreview( EMBEDDABLE_URL ); - // Trigger generator - fulfillment.next(); - // Provide apiFetch response and trigger Action - const received = ( await fulfillment.next( SUCCESSFUL_EMBED_RESPONSE ) ) - .value; - expect( received ).toEqual( - receiveEmbedPreview( EMBEDDABLE_URL, SUCCESSFUL_EMBED_RESPONSE ) + const dispatch = Object.assign( jest.fn(), { + receiveEmbedPreview: jest.fn(), + } ); + + // Provide response + triggerFetch.mockResolvedValue( SUCCESSFUL_EMBED_RESPONSE ); + + await getEmbedPreview( EMBEDDABLE_URL )( { dispatch } ); + + expect( dispatch.receiveEmbedPreview ).toHaveBeenCalledWith( + EMBEDDABLE_URL, + SUCCESSFUL_EMBED_RESPONSE ); } ); it( 'yields false if the URL cannot be embedded', async () => { - const fulfillment = getEmbedPreview( UNEMBEDDABLE_URL ); - // Trigger generator - fulfillment.next(); - // Provide invalid response and trigger Action - const received = ( await fulfillment.throw( { status: 404 } ) ).value; - expect( received ).toEqual( - receiveEmbedPreview( UNEMBEDDABLE_URL, UNEMBEDDABLE_RESPONSE ) + const dispatch = Object.assign( jest.fn(), { + receiveEmbedPreview: jest.fn(), + } ); + + // Provide response + triggerFetch.mockRejectedValue( { status: 404 } ); + + await getEmbedPreview( UNEMBEDDABLE_URL )( { dispatch } ); + + expect( dispatch.receiveEmbedPreview ).toHaveBeenCalledWith( + UNEMBEDDABLE_URL, + UNEMBEDDABLE_RESPONSE ); } ); } ); @@ -439,14 +447,17 @@ describe( 'getCurrentUser', () => { }; it( 'yields with fetched user', async () => { - const fulfillment = getCurrentUser(); + const dispatch = Object.assign( jest.fn(), { + receiveCurrentUser: jest.fn(), + } ); - // Trigger generator - fulfillment.next(); + // Provide response + triggerFetch.mockResolvedValue( SUCCESSFUL_RESPONSE ); + + await getCurrentUser()( { dispatch } ); - // Provide apiFetch response and trigger Action - const received = ( await fulfillment.next( SUCCESSFUL_RESPONSE ) ) - .value; - expect( received ).toEqual( receiveCurrentUser( SUCCESSFUL_RESPONSE ) ); + expect( dispatch.receiveCurrentUser ).toHaveBeenCalledWith( + SUCCESSFUL_RESPONSE + ); } ); } ); From e38c1d3f44fd9975cb13e9ad006b96c235f91fe0 Mon Sep 17 00:00:00 2001 From: Rich Tabor <hi@richtabor.com> Date: Tue, 7 Sep 2021 09:53:09 -0400 Subject: [PATCH 194/214] Use blockGap between Columns blocks (#34456) * Replace 1em and 2em with var(--wp--style--block-gap) appropriately * Use var(--wp--style--block-gap) in editor styles for cols * Add 2em fallback --- packages/block-library/src/columns/editor.scss | 4 ++-- packages/block-library/src/columns/style.scss | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss index bb84ad88a66232..de64661b46b4bf 100644 --- a/packages/block-library/src/columns/editor.scss +++ b/packages/block-library/src/columns/editor.scss @@ -12,14 +12,14 @@ @include break-small() { .editor-styles-wrapper .block-editor-block-list__block.wp-block-column:nth-child(even) { - margin-left: $grid-unit-20 * 2; + margin-left: var(--wp--style--block-gap, 2em); } } @include break-medium() { .editor-styles-wrapper .block-editor-block-list__block.wp-block-column:not(:first-child) { - margin-left: $grid-unit-20 * 2; + margin-left: var(--wp--style--block-gap, 2em); } } diff --git a/packages/block-library/src/columns/style.scss b/packages/block-library/src/columns/style.scss index aba4c99297a191..acd1d713385bd8 100644 --- a/packages/block-library/src/columns/style.scss +++ b/packages/block-library/src/columns/style.scss @@ -43,14 +43,14 @@ // As with mobile styles, this must be important since the Column // assigns its own width as an inline style, which should take effect // starting at `break-medium`. - flex-basis: calc(50% - 1em) !important; + flex-basis: calc(50% - calc(var(--wp--style--block-gap, 2em) / 2)) !important; flex-grow: 0; } // Add space between the multiple columns. Themes can customize this if they wish to work differently. // Only apply this beyond the mobile breakpoint, as there's only a single column on mobile. &:nth-child(even) { - margin-left: 2em; + margin-left: var(--wp--style--block-gap, 2em); } } @@ -74,7 +74,7 @@ // When columns are in a single row, add space before all except the first. &:not(:first-child) { - margin-left: 2em; + margin-left: var(--wp--style--block-gap, 2em); } } } @@ -95,7 +95,7 @@ // When columns are in a single row, add space before all except the first. &:not(:first-child) { - margin-left: 2em; + margin-left: var(--wp--style--block-gap, 2em); } } } From ffe070706253465a9c3372429c81b32daa160527 Mon Sep 17 00:00:00 2001 From: Adam Zielinski <adam@adamziel.com> Date: Tue, 7 Sep 2021 17:05:37 +0200 Subject: [PATCH 195/214] Migrate entities.js to thunks (#34582) * Migrate entities.js to thunks * Remove integration.js for now * Restore POST_RAW_ATTRIBUTES --- packages/core-data/src/entities.js | 26 +-- packages/core-data/src/test/entities.js | 66 +++--- packages/core-data/src/test/integration.js | 256 --------------------- 3 files changed, 50 insertions(+), 298 deletions(-) delete mode 100644 packages/core-data/src/test/integration.js diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index c1783c85850b31..e00e5f37319d88 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -6,15 +6,13 @@ import { upperFirst, camelCase, map, find, get, startCase } from 'lodash'; /** * WordPress dependencies */ -import { controls } from '@wordpress/data'; -import { apiFetch } from '@wordpress/data-controls'; +import apiFetch from '@wordpress/api-fetch'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import { addEntities } from './actions'; -import { STORE_NAME } from './name'; export const DEFAULT_ENTITY_KEY = 'id'; @@ -170,8 +168,8 @@ export const prePersistPostType = ( persistedRecord, edits ) => { * * @return {Promise} Entities promise */ -function* loadPostTypeEntities() { - const postTypes = yield apiFetch( { path: '/wp/v2/types?context=edit' } ); +async function loadPostTypeEntities() { + const postTypes = await apiFetch( { path: '/wp/v2/types?context=edit' } ); return map( postTypes, ( postType, name ) => { const isTemplate = [ 'wp_template', 'wp_template_part' ].includes( name @@ -203,8 +201,8 @@ function* loadPostTypeEntities() { * * @return {Promise} Entities promise */ -function* loadTaxonomyEntities() { - const taxonomies = yield apiFetch( { +async function loadTaxonomyEntities() { + const taxonomies = await apiFetch( { path: '/wp/v2/taxonomies?context=edit', } ); return map( taxonomies, ( taxonomy, name ) => { @@ -252,12 +250,8 @@ export const getMethodName = ( * * @return {Array} Entities */ -export function* getKindEntities( kind ) { - let entities = yield controls.select( - STORE_NAME, - 'getEntitiesByKind', - kind - ); +export const getKindEntities = ( kind ) => async ( { select, dispatch } ) => { + let entities = select.getEntitiesByKind( kind ); if ( entities && entities.length !== 0 ) { return entities; } @@ -267,8 +261,8 @@ export function* getKindEntities( kind ) { return []; } - entities = yield kindConfig.loadEntities(); - yield addEntities( entities ); + entities = await kindConfig.loadEntities(); + dispatch( addEntities( entities ) ); return entities; -} +}; diff --git a/packages/core-data/src/test/entities.js b/packages/core-data/src/test/entities.js index ee568902468205..7c654b8d1732fa 100644 --- a/packages/core-data/src/test/entities.js +++ b/packages/core-data/src/test/entities.js @@ -1,3 +1,9 @@ +/** + * WordPress dependencies + */ +import triggerFetch from '@wordpress/api-fetch'; +jest.mock( '@wordpress/api-fetch' ); + /** * Internal dependencies */ @@ -7,7 +13,6 @@ import { getKindEntities, prePersistPostType, } from '../entities'; -import { addEntities } from '../actions'; describe( 'getMethodName', () => { it( 'should return the right method name for an entity with the root kind', () => { @@ -45,43 +50,52 @@ describe( 'getMethodName', () => { } ); describe( 'getKindEntities', () => { + beforeEach( async () => { + triggerFetch.mockReset(); + jest.useFakeTimers(); + } ); + it( 'shouldn’t do anything if the entities have already been resolved', async () => { + const dispatch = jest.fn(); + const select = { + getEntitiesByKind: jest.fn( () => entities ), + }; const entities = [ { kind: 'postType' } ]; - const fulfillment = getKindEntities( 'postType' ); - // Start the generator - fulfillment.next(); - // Provide the entities - const end = fulfillment.next( entities ); - expect( end.done ).toBe( true ); + await getKindEntities( 'postType' )( { dispatch, select } ); + expect( dispatch ).not.toHaveBeenCalled(); } ); it( 'shouldn’t do anything if there no defined kind config', async () => { - const fulfillment = getKindEntities( 'unknownKind' ); - // Start the generator - fulfillment.next(); - // Provide no entities to continue - const end = fulfillment.next( [] ); - expect( end.done ).toBe( true ); + const dispatch = jest.fn(); + const select = { + getEntitiesByKind: jest.fn( () => [] ), + }; + await getKindEntities( 'unknownKind' )( { dispatch, select } ); + expect( dispatch ).not.toHaveBeenCalled(); } ); it( 'should fetch and add the entities', async () => { const fetchedEntities = [ { - baseURL: '/wp/v2/posts', - kind: 'postType', - name: 'post', + rest_base: 'posts', + labels: { + singular_name: 'post', + }, }, ]; - const fulfillment = getKindEntities( 'postType' ); - // Start the generator - fulfillment.next(); - // Provide no entities to continue - fulfillment.next( [] ); - // Fetch entities and trigger action - const { value: action } = fulfillment.next( fetchedEntities ); - expect( action ).toEqual( addEntities( fetchedEntities ) ); - const end = fulfillment.next(); - expect( end ).toEqual( { done: true, value: fetchedEntities } ); + const dispatch = jest.fn(); + const select = { + getEntitiesByKind: jest.fn( () => [] ), + }; + triggerFetch.mockImplementation( () => fetchedEntities ); + + await getKindEntities( 'postType' )( { dispatch, select } ); + expect( dispatch ).toHaveBeenCalledTimes( 1 ); + expect( dispatch.mock.calls[ 0 ][ 0 ].type ).toBe( 'ADD_ENTITIES' ); + expect( dispatch.mock.calls[ 0 ][ 0 ].entities.length ).toBe( 1 ); + expect( dispatch.mock.calls[ 0 ][ 0 ].entities[ 0 ].baseURL ).toBe( + '/wp/v2/posts' + ); } ); } ); diff --git a/packages/core-data/src/test/integration.js b/packages/core-data/src/test/integration.js deleted file mode 100644 index 7411e560436c0d..00000000000000 --- a/packages/core-data/src/test/integration.js +++ /dev/null @@ -1,256 +0,0 @@ -/** - * WordPress dependencies - */ -import { createRegistry, controls } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import * as actions from '../actions'; -import * as selectors from '../selectors'; -import * as resolvers from '../resolvers'; -import { store } from '../'; - -// Mock to prevent calling window.fetch in test environment -jest.mock( '@wordpress/data-controls', () => { - const dataControls = jest.requireActual( '@wordpress/data-controls' ); - return { - ...dataControls, - apiFetch: jest.fn(), - }; -} ); - -jest.mock( '@wordpress/api-fetch', () => { - return { - __esModule: true, - default: jest.fn(), - }; -} ); -import triggerFetch from '@wordpress/api-fetch'; - -const runPromise = async ( promise ) => { - jest.runAllTimers(); - await promise; -}; - -const runPendingPromises = async () => { - // @TODO: find a better way of exhausting the current event loop queue - for ( let i = 0; i < 100; i++ ) { - jest.runAllTimers(); - const p = new Promise( ( resolve ) => setTimeout( resolve ) ); - jest.runAllTimers(); - await p; - } -}; - -describe( 'receiveEntityRecord', () => { - function createTestRegistry( getEntityRecord ) { - const registry = createRegistry(); - const initialState = { - entities: { - data: {}, - }, - }; - registry.register( store ); - registry.registerStore( 'test/resolution', { - actions: { - __unstableAcquireStoreLock: () => ( { type: 'ACQUIRE_LOCK' } ), - __unstableReleaseStoreLock: () => ( { type: 'RELEASE_LOCK' } ), - receiveEntityRecords: actions.receiveEntityRecords, - getEntityRecords( ...args ) { - return resolvers.getEntityRecords( ...args ); - }, - *getEntityRecord( ...args ) { - return yield controls.resolveSelect( - 'test/resolution', - 'getEntityRecord', - ...args - ); - }, - }, - reducer: ( state = initialState ) => { - return state; - }, - selectors: { - getEntityRecord: selectors.getEntityRecord, - getEntityRecords: selectors.getEntityRecords, - }, - resolvers: { - getEntityRecord, - getEntityRecords: resolvers.getEntityRecords, - }, - __experimentalUseThunks: true, - } ); - return registry; - } - - beforeEach( async () => { - triggerFetch.mockReset(); - jest.useFakeTimers(); - } ); - - it( 'should not trigger a resolver when the requested record is available via receiveEntityRecords (default entity key).', async () => { - const getEntityRecord = jest.fn(); - const registry = createTestRegistry( getEntityRecord ); - - // // Trigger resolution of postType records - triggerFetch.mockImplementation( () => ( { - 2: { slug: 'test', id: 2 }, - } ) ); - await runPromise( - registry - .dispatch( 'test/resolution' ) - .getEntityRecords( 'root', 'site' ) - ); - jest.runAllTimers(); - - // Select record with id = 2, it is available and should not trigger the resolver - await runPromise( - registry - .dispatch( 'test/resolution' ) - .getEntityRecord( 'root', 'site', 2 ) - ); - expect( getEntityRecord ).not.toHaveBeenCalled(); - - // Select record with id = 4, it is not available and should trigger the resolver - await runPromise( - registry - .dispatch( 'test/resolution' ) - .getEntityRecord( 'root', 'site', 4 ) - ); - expect( getEntityRecord ).toHaveBeenCalled(); - } ); - - it( 'should not trigger a resolver when the requested record is available via receiveEntityRecords (non-default entity key).', async () => { - const getEntityRecord = jest.fn(); - const registry = createTestRegistry( getEntityRecord ); - - // Trigger resolution of postType records - triggerFetch.mockImplementation( () => ( { - 'test-1': { slug: 'test-1', id: 2 }, - } ) ); - await runPromise( - registry - .dispatch( 'test/resolution' ) - .getEntityRecords( 'root', 'taxonomy' ) - ); - jest.runAllTimers(); - - // Select record with id = test-1, it is available and should not trigger the resolver - await runPromise( - registry - .dispatch( 'test/resolution' ) - .getEntityRecord( 'root', 'taxonomy', 'test-1' ) - ); - expect( getEntityRecord ).not.toHaveBeenCalled(); - - // Select record with id = test-2, it is not available and should trigger the resolver - await runPromise( - registry - .dispatch( 'test/resolution' ) - .getEntityRecord( 'root', 'taxonomy', 'test-2' ) - ); - expect( getEntityRecord ).toHaveBeenCalled(); - } ); -} ); - -describe( 'saveEntityRecord', () => { - function createTestRegistry() { - const registry = createRegistry(); - registry.register( store ); - return registry; - } - - beforeEach( async () => { - triggerFetch.mockReset(); - jest.useFakeTimers( 'modern' ); - } ); - - it( 'should not trigger any GET requests until POST/PUT is finished.', async () => { - const registry = createTestRegistry(); - // Fetch post types from the API {{{ - triggerFetch.mockImplementation( () => ( { - 'post-1': { slug: 'post-1' }, - } ) ); - - // Trigger fetch - registry.select( 'core' ).getEntityRecords( 'root', 'postType' ); - await runPendingPromises(); - expect( triggerFetch ).toBeCalledTimes( 1 ); - expect( triggerFetch ).toBeCalledWith( { - path: '/wp/v2/types?context=edit', - } ); - - // Select fetched results, there should be no subsequent request - triggerFetch.mockReset(); - const results = registry - .select( 'core' ) - .getEntityRecords( 'root', 'postType' ); - expect( triggerFetch ).toBeCalledTimes( 0 ); - expect( results ).toHaveLength( 1 ); - expect( results[ 0 ].slug ).toBe( 'post-1' ); - // }}} Fetch post types from the API - - // Save changes - triggerFetch.mockClear(); - let resolvePromise; - triggerFetch.mockImplementation( function () { - return new Promise( ( resolve ) => { - resolvePromise = resolve; - } ); - } ); - const savePromise = registry - .dispatch( 'core' ) - .saveEntityRecord( 'root', 'postType', { - slug: 'post-1', - newField: 'a', - } ); - - await runPendingPromises(); - - // There should ONLY be a single hanging API call (PUT) by this point. - // If there have been any other requests, it is a race condition of some sorts, - // e.g. a resolution was triggered before the save was finished. - expect( triggerFetch ).toBeCalledTimes( 1 ); - expect( triggerFetch ).toHaveBeenCalledWith( - expect.objectContaining( { - method: 'PUT', - path: '/wp/v2/types/post-1', - data: expect.objectContaining( { - newField: 'a', - slug: 'post-1', - } ), - } ) - ); - triggerFetch.mockClear(); - - // The PUT is still hanging, let's call a selector now and make sure it won't trigger - // any requests - registry.select( 'core' ).getEntityRecords( 'root', 'postType' ); - jest.runAllTimers(); - expect( triggerFetch ).toBeCalledTimes( 0 ); - - // Now that all timers are exhausted, let's resolve the PUT request and wait until the - // save is complete - resolvePromise( { newField: 'a', slug: 'post-1' } ); - - // Run selector and make sure it doesn't trigger any requests just yet - registry.select( 'core' ).getEntityRecords( 'root', 'postType' ); - jest.runAllTimers(); - expect( triggerFetch ).toBeCalledTimes( 0 ); - - const newRecord = await savePromise; - expect( newRecord ).toEqual( { newField: 'a', slug: 'post-1' } ); - // There should be no other API calls just because saving succeeded - jest.runAllTimers(); - expect( triggerFetch ).toBeCalledTimes( 0 ); - - // Calling the selector after the save is finished should trigger a resolver and a GET request - registry.select( 'core' ).getEntityRecords( 'root', 'postType' ); - await runPendingPromises(); - expect( triggerFetch ).toBeCalledTimes( 1 ); - expect( triggerFetch ).toBeCalledWith( { - path: '/wp/v2/types?context=edit', - } ); - } ); -} ); From 5341bacb40b30d00dc6183e3c0e1f3835f9efde7 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 7 Sep 2021 16:42:24 +0100 Subject: [PATCH 196/214] Group block: Add a row variation (#34535) --- lib/block-supports/layout.php | 11 ++++++----- packages/block-library/src/group/edit.js | 10 ++++++---- packages/block-library/src/group/index.js | 2 ++ packages/block-library/src/group/variations.js | 18 ++++++++++++++++++ 4 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 packages/block-library/src/group/variations.js diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 92763e010d7756..71fb0ac6817ea5 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -160,11 +160,11 @@ function () use ( $style ) { */ function gutenberg_restore_group_inner_container( $block_content, $block ) { $group_with_inner_container_regex = '/(^\s*<div\b[^>]*wp-block-group(\s|")[^>]*>)(\s*<div\b[^>]*wp-block-group__inner-container(\s|")[^>]*>)((.|\S|\s)*)/'; - if ( 'core/group' !== $block['blockName'] || WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() || - 1 === preg_match( $group_with_inner_container_regex, $block_content ) + 1 === preg_match( $group_with_inner_container_regex, $block_content ) || + ( isset( $block['attrs']['layout']['type'] ) && 'default' !== $block['attrs']['layout']['type'] ) ) { return $block_content; } @@ -180,7 +180,8 @@ function( $matches ) { return $updated_content; } -// This can be removed when plugin support requires WordPress 5.8.0+. -if ( ! function_exists( 'wp_restore_group_inner_container' ) ) { - add_filter( 'render_block', 'gutenberg_restore_group_inner_container', 10, 2 ); +if ( function_exists( 'wp_restore_group_inner_container' ) ) { + remove_filter( 'render_block', 'wp_restore_group_inner_container', 10, 2 ); } +add_filter( 'render_block', 'gutenberg_restore_group_inner_container', 10, 2 ); + diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index c82ab09a1709af..f30a7482bc3d81 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -28,10 +28,12 @@ function GroupEdit( { attributes, setAttributes, clientId } ) { const defaultLayout = useSetting( 'layout' ) || {}; const { tagName: TagName = 'div', templateLock, layout = {} } = attributes; const usedLayout = !! layout && layout.inherit ? defaultLayout : layout; + const { type = 'default' } = usedLayout; + const layoutSupportEnabled = themeSupportsLayout || type !== 'default'; const blockProps = useBlockProps(); const innerBlocksProps = useInnerBlocksProps( - themeSupportsLayout + layoutSupportEnabled ? blockProps : { className: 'wp-block-group__inner-container' }, { @@ -39,7 +41,7 @@ function GroupEdit( { attributes, setAttributes, clientId } ) { renderAppender: hasInnerBlocks ? undefined : InnerBlocks.ButtonBlockAppender, - __experimentalLayout: themeSupportsLayout ? usedLayout : undefined, + __experimentalLayout: layoutSupportEnabled ? usedLayout : undefined, } ); @@ -63,10 +65,10 @@ function GroupEdit( { attributes, setAttributes, clientId } ) { } /> </InspectorControls> - { themeSupportsLayout && <TagName { ...innerBlocksProps } /> } + { layoutSupportEnabled && <TagName { ...innerBlocksProps } /> } { /* Ideally this is not needed but it's there for backward compatibility reason to keep this div for themes that might rely on its presence */ } - { ! themeSupportsLayout && ( + { ! layoutSupportEnabled && ( <TagName { ...blockProps }> <div { ...innerBlocksProps } /> </TagName> diff --git a/packages/block-library/src/group/index.js b/packages/block-library/src/group/index.js index dac9a09b3cb1fa..e3b9d887d95f87 100644 --- a/packages/block-library/src/group/index.js +++ b/packages/block-library/src/group/index.js @@ -12,6 +12,7 @@ import deprecated from './deprecated'; import edit from './edit'; import metadata from './block.json'; import save from './save'; +import variations from './variations'; const { name } = metadata; @@ -135,4 +136,5 @@ export const settings = { edit, save, deprecated, + variations, }; diff --git a/packages/block-library/src/group/variations.js b/packages/block-library/src/group/variations.js new file mode 100644 index 00000000000000..76de042661f38d --- /dev/null +++ b/packages/block-library/src/group/variations.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +const variations = [ + { + name: 'group-row', + title: __( 'Row' ), + description: __( 'Blocks shown in a row.' ), + attributes: { layout: { type: 'flex' } }, + scope: [ 'inserter' ], + isActive: ( blockAttributes ) => + blockAttributes.layout?.type === 'flex', + }, +]; + +export default variations; From 435119c3bed91c10737a574e072d7482e7e5c6bb Mon Sep 17 00:00:00 2001 From: Enej Bajgoric <enej.bajgoric@automattic.com> Date: Tue, 7 Sep 2021 08:56:36 -0700 Subject: [PATCH 197/214] [Mobile] Update the bottom sheet header (#34309) * Add new Header bottomSheet component * Include the new Header component in buttomSheet * Update NavigationHeader to use the new Header component for backwards compatibility * Update all instances of NavigationHeader with Header * Update BottomSheet Header documentation to reduce its size (#34339) Leverage short, declarative statements when possible. * Move Header to NavBar * Update usage from Header to NavBar * Remove the NavigationHeader component * Fix space in the styles Co-authored-by: David Calhoun <438664+dcalhoun@users.noreply.github.com> --- .../src/font-size-picker/index.native.js | 10 +- .../index.native.js | 10 +- .../bottom-sheet-text-control/index.native.js | 10 +- .../src/mobile/bottom-sheet/index.native.js | 4 +- .../src/mobile/bottom-sheet/nav-bar/README.md | 61 ++++++++ .../nav-bar/action-button.native.js | 30 ++++ .../nav-bar/apply-button.native.js | 53 +++++++ .../nav-bar/back-button.native.js | 94 +++++++++++ .../bottom-sheet/nav-bar/heading.native.js | 33 ++++ .../bottom-sheet/nav-bar/index.native.js | 23 +++ .../bottom-sheet/nav-bar/styles.native.scss | 69 ++++++++ .../bottom-sheet/navigation-header.native.js | 147 ------------------ .../mobile/bottom-sheet/sub-sheet/README.md | 8 +- .../gradient-picker-screen.native.js | 10 +- .../color-settings/palette.screen.native.js | 10 +- .../index.native.js | 19 ++- .../src/mobile/link-picker/index.native.js | 13 +- .../help-detail-navigation-screen.native.js | 12 +- .../components/editor-help/index.native.js | 24 +-- 19 files changed, 437 insertions(+), 203 deletions(-) create mode 100644 packages/components/src/mobile/bottom-sheet/nav-bar/README.md create mode 100644 packages/components/src/mobile/bottom-sheet/nav-bar/action-button.native.js create mode 100644 packages/components/src/mobile/bottom-sheet/nav-bar/apply-button.native.js create mode 100644 packages/components/src/mobile/bottom-sheet/nav-bar/back-button.native.js create mode 100644 packages/components/src/mobile/bottom-sheet/nav-bar/heading.native.js create mode 100644 packages/components/src/mobile/bottom-sheet/nav-bar/index.native.js create mode 100644 packages/components/src/mobile/bottom-sheet/nav-bar/styles.native.scss delete mode 100644 packages/components/src/mobile/bottom-sheet/navigation-header.native.js diff --git a/packages/components/src/font-size-picker/index.native.js b/packages/components/src/font-size-picker/index.native.js index 2be833c450a82a..1da82363c47896 100644 --- a/packages/components/src/font-size-picker/index.native.js +++ b/packages/components/src/font-size-picker/index.native.js @@ -84,10 +84,12 @@ function FontSizePicker( { showSheet={ showSubSheet } > <> - <BottomSheet.NavigationHeader - screen={ label } - leftButtonOnPress={ goBack } - /> + <BottomSheet.NavBar> + <BottomSheet.NavBar.BackButton onPress={ goBack } /> + <BottomSheet.NavBar.Heading> + { label } + </BottomSheet.NavBar.Heading> + </BottomSheet.NavBar> <View style={ styles[ 'components-font-size-picker' ] }> <BottomSheet.Cell customActionButton diff --git a/packages/components/src/mobile/bottom-sheet-select-control/index.native.js b/packages/components/src/mobile/bottom-sheet-select-control/index.native.js index 5f2ec0aee79979..bc4a828dd9bdfd 100644 --- a/packages/components/src/mobile/bottom-sheet-select-control/index.native.js +++ b/packages/components/src/mobile/bottom-sheet-select-control/index.native.js @@ -70,10 +70,12 @@ const BottomSheetSelectControl = ( { showSheet={ showSubSheet } > <> - <BottomSheet.NavigationHeader - screen={ label } - leftButtonOnPress={ goBack } - /> + <BottomSheet.NavBar> + <BottomSheet.NavBar.BackButton onPress={ goBack } /> + <BottomSheet.NavBar.Heading> + { label } + </BottomSheet.NavBar.Heading> + </BottomSheet.NavBar> <View style={ styles.selectControl }> { items.map( ( item, index ) => ( <BottomSheet.Cell diff --git a/packages/components/src/mobile/bottom-sheet-text-control/index.native.js b/packages/components/src/mobile/bottom-sheet-text-control/index.native.js index 1244e2b686aa04..820f8e16da2960 100644 --- a/packages/components/src/mobile/bottom-sheet-text-control/index.native.js +++ b/packages/components/src/mobile/bottom-sheet-text-control/index.native.js @@ -68,10 +68,12 @@ const BottomSheetTextControl = ( { showSheet={ showSubSheet } > <> - <BottomSheet.NavigationHeader - screen={ label } - leftButtonOnPress={ goBack } - /> + <BottomSheet.NavBar> + <BottomSheet.NavBar.BackButton onPress={ goBack } /> + <BottomSheet.NavBar.Heading> + { label } + </BottomSheet.NavBar.Heading> + </BottomSheet.NavBar> <PanelBody style={ horizontalBorderStyle }> <TextInput label={ label } diff --git a/packages/components/src/mobile/bottom-sheet/index.native.js b/packages/components/src/mobile/bottom-sheet/index.native.js index 27e16192705e73..777a17ecb25926 100644 --- a/packages/components/src/mobile/bottom-sheet/index.native.js +++ b/packages/components/src/mobile/bottom-sheet/index.native.js @@ -42,7 +42,7 @@ import NavigationScreen from './bottom-sheet-navigation/navigation-screen'; import NavigationContainer from './bottom-sheet-navigation/navigation-container'; import KeyboardAvoidingView from './keyboard-avoiding-view'; import BottomSheetSubSheet from './sub-sheet'; -import NavigationHeader from './navigation-header'; +import NavBar from './nav-bar'; import { BottomSheetProvider } from './bottom-sheet-context'; const DEFAULT_LAYOUT_ANIMATION = LayoutAnimation.Presets.easeInEaseOut; @@ -609,7 +609,7 @@ ThemedBottomSheet.getWidth = getWidth; ThemedBottomSheet.Button = Button; ThemedBottomSheet.Cell = Cell; ThemedBottomSheet.SubSheet = BottomSheetSubSheet; -ThemedBottomSheet.NavigationHeader = NavigationHeader; +ThemedBottomSheet.NavBar = NavBar; ThemedBottomSheet.CyclePickerCell = CyclePickerCell; ThemedBottomSheet.PickerCell = PickerCell; ThemedBottomSheet.SwitchCell = SwitchCell; diff --git a/packages/components/src/mobile/bottom-sheet/nav-bar/README.md b/packages/components/src/mobile/bottom-sheet/nav-bar/README.md new file mode 100644 index 00000000000000..76f1c19b14501a --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/nav-bar/README.md @@ -0,0 +1,61 @@ +# BottomSheet Header + +BottomSheet Header components provide styled elements for composing header UI within a `BottomSheet`. + +## Usage + +```jsx +import { BottomSheet } from '@wordpress/components'; + +export default = () => ( + <BottomSheet> + <BottomSheet.NavBar> + <BottomSheet.NavBar.BackButton onPress={ () => {} } /> + <BottomSheet.NavBar.Title>A Sheet Title</BottomSheet.NavBar.Title> + <BottomSheet.NavBar.ApplyButton onPress={ () => {} } /> + </BottomSheet.NavBar> + </BottomSheet> +); +``` + +## BottomSheet.NavBar + +Provides structural styles for left-center-right layout for header UI. + +## BottomSheet.NavBar.Title + +Displays a styled title for a bottom sheet. + +## BottomSheet.NavBar.ApplyButton + +Displays a styled button to apply settings of bottom sheet controls. + +### Props + +#### onPress + +Callback invoked once the button is pressed. + +## BottomSheet.NavBar.BackButton + +Displays a styled button to navigate backwards from a bottom sheet. + +### Props + +#### onPress + +Callback invoked once the button is pressed. + +## BottomSheet.NavBar.DismissButton + +Displays a styled button to dismiss a full screen bottom sheet. + +### Props + +#### onPress + +Callback invoked once the button is pressed. + +#### iosText + +Used to display iOS text if different from "Cancel". \ No newline at end of file diff --git a/packages/components/src/mobile/bottom-sheet/nav-bar/action-button.native.js b/packages/components/src/mobile/bottom-sheet/nav-bar/action-button.native.js new file mode 100644 index 00000000000000..6b11f30959fe5d --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/nav-bar/action-button.native.js @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import { View, TouchableWithoutFeedback } from 'react-native'; + +/** + * Internal dependencies + */ +import styles from './styles.scss'; + +// Action button component is used by both Back and Apply Button componenets. +function ActionButton( { + onPress, + accessibilityLabel, + accessibilityHint, + children, +} ) { + return ( + <TouchableWithoutFeedback + onPress={ onPress } + accessibilityRole={ 'button' } + accessibilityLabel={ accessibilityLabel } + accessibilityHint={ accessibilityHint } + > + <View style={ styles[ 'action-button' ] }>{ children }</View> + </TouchableWithoutFeedback> + ); +} + +export default ActionButton; diff --git a/packages/components/src/mobile/bottom-sheet/nav-bar/apply-button.native.js b/packages/components/src/mobile/bottom-sheet/nav-bar/apply-button.native.js new file mode 100644 index 00000000000000..a5362f1a67a098 --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/nav-bar/apply-button.native.js @@ -0,0 +1,53 @@ +/** + * External dependencies + */ +import { View, Text, Platform } from 'react-native'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Icon, check } from '@wordpress/icons'; +import { usePreferredColorSchemeStyle } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import styles from './styles.scss'; +import ActionButton from './action-button'; + +function ApplyButton( { onPress } ) { + const buttonTextStyle = usePreferredColorSchemeStyle( + styles[ 'button-text' ], + styles[ 'button-text-dark' ] + ); + + const applyButtonStyle = usePreferredColorSchemeStyle( + styles[ 'apply-button-icon' ], + styles[ 'apply-button-icon-dark' ] + ); + + return ( + <View style={ styles[ 'apply-button' ] }> + <ActionButton + onPress={ onPress } + accessibilityLabel={ __( 'Apply' ) } + accessibilityHint={ __( 'Applies the setting' ) } + > + { Platform.OS === 'ios' ? ( + <Text style={ buttonTextStyle } maxFontSizeMultiplier={ 2 }> + { __( 'Apply' ) } + </Text> + ) : ( + <Icon + icon={ check } + size={ 24 } + style={ applyButtonStyle } + /> + ) } + </ActionButton> + </View> + ); +} + +export default ApplyButton; diff --git a/packages/components/src/mobile/bottom-sheet/nav-bar/back-button.native.js b/packages/components/src/mobile/bottom-sheet/nav-bar/back-button.native.js new file mode 100644 index 00000000000000..859eba98b9604a --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/nav-bar/back-button.native.js @@ -0,0 +1,94 @@ +/** + * External dependencies + */ +import { View, Platform, Text } from 'react-native'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Icon, arrowLeft, close } from '@wordpress/icons'; +import { usePreferredColorSchemeStyle } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import styles from './styles.scss'; +import ActionButton from './action-button'; +import chevronBack from './../chevron-back'; + +function Button( { onPress, icon, text } ) { + const buttonTextStyle = usePreferredColorSchemeStyle( + styles[ 'button-text' ], + styles[ 'button-text-dark' ] + ); + + return ( + <View style={ styles[ 'back-button' ] }> + <ActionButton + onPress={ onPress } + accessibilityLabel={ __( 'Go back' ) } + accessibilityHint={ __( + 'Navigates to the previous content sheet' + ) } + > + { icon } + { text && ( + <Text style={ buttonTextStyle } maxFontSizeMultiplier={ 2 }> + { text } + </Text> + ) } + </ActionButton> + </View> + ); +} + +function BackButton( { onPress } ) { + const chevronLeftStyle = usePreferredColorSchemeStyle( + styles[ 'chevron-left-icon' ], + styles[ 'chevron-left-icon-dark' ] + ); + const arrowLeftStyle = usePreferredColorSchemeStyle( + styles[ 'arrow-left-icon' ], + styles[ 'arrow-right-icon-dark' ] + ); + + let backIcon; + let backText; + + if ( Platform.OS === 'ios' ) { + backIcon = ( + <Icon icon={ chevronBack } size={ 21 } style={ chevronLeftStyle } /> + ); + backText = __( 'Back' ); + } else { + backIcon = ( + <Icon icon={ arrowLeft } size={ 24 } style={ arrowLeftStyle } /> + ); + } + + return <Button onPress={ onPress } icon={ backIcon } text={ backText } />; +} + +function DismissButton( { onPress, iosText } ) { + const arrowLeftStyle = usePreferredColorSchemeStyle( + styles[ 'arrow-left-icon' ], + styles[ 'arrow-right-icon-dark' ] + ); + + let backIcon; + let backText; + + if ( Platform.OS === 'ios' ) { + backText = iosText ? iosText : __( 'Cancel' ); + } else { + backIcon = <Icon icon={ close } size={ 24 } style={ arrowLeftStyle } />; + } + + return <Button onPress={ onPress } icon={ backIcon } text={ backText } />; +} + +Button.Back = BackButton; +Button.Dismiss = DismissButton; // Cancel or Close Button + +export default Button; diff --git a/packages/components/src/mobile/bottom-sheet/nav-bar/heading.native.js b/packages/components/src/mobile/bottom-sheet/nav-bar/heading.native.js new file mode 100644 index 00000000000000..9bbae77dbde5e9 --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/nav-bar/heading.native.js @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import { Text } from 'react-native'; + +/** + * WordPress dependencies + */ +import { usePreferredColorSchemeStyle } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import styles from './styles.scss'; + +function Heading( { children } ) { + const headingStyle = usePreferredColorSchemeStyle( + styles.heading, + styles[ 'heading-dark' ] + ); + + return ( + <Text + accessibilityRole="header" + style={ headingStyle } + maxFontSizeMultiplier={ 3 } + > + { children } + </Text> + ); +} + +export default Heading; diff --git a/packages/components/src/mobile/bottom-sheet/nav-bar/index.native.js b/packages/components/src/mobile/bottom-sheet/nav-bar/index.native.js new file mode 100644 index 00000000000000..57b4c23d4b49a7 --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/nav-bar/index.native.js @@ -0,0 +1,23 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; + +/** + * Internal dependencies + */ +import ApplyButton from './apply-button'; +import Button from './back-button'; +import Heading from './heading'; +import styles from './styles.scss'; +function NavBar( { children } ) { + return <View style={ styles[ 'nav-bar' ] }>{ children }</View>; +} + +NavBar.ApplyButton = ApplyButton; +NavBar.BackButton = Button.Back; +NavBar.DismissButton = Button.Dismiss; + +NavBar.Heading = Heading; + +export default NavBar; diff --git a/packages/components/src/mobile/bottom-sheet/nav-bar/styles.native.scss b/packages/components/src/mobile/bottom-sheet/nav-bar/styles.native.scss new file mode 100644 index 00000000000000..a6fe92ea37626a --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/nav-bar/styles.native.scss @@ -0,0 +1,69 @@ +.nav-bar { + align-items: center; + flex-direction: row; + height: 44px; + justify-content: center; +} + +.heading { + color: $light-primary; + text-align: center; + font-weight: 600; + font-size: 16px; + position: absolute; + width: 100%; +} + +.heading-dark { + color: $dark-primary; +} + +.action-button { + align-items: center; + flex-direction: row; + height: 100%; + justify-content: center; + min-width: 44px; + padding-left: $grid-unit-20; + padding-right: $grid-unit-20; +} + +.back-button { + align-items: flex-start; + flex: 1; + justify-content: center; + z-index: 2; +} + +.apply-button { + align-items: flex-end; + flex: 1; + justify-content: center; + z-index: 2; +} + +.button-text { + color: $blue-50; + font-size: 16px; +} + +.button-text-dark { + color: $blue-30; +} + +.chevron-left-icon { + color: $blue-50; + margin-left: -11px; +} + +.chevron-left-icon-dark { + color: $blue-30; +} + +.arrow-left-icon { + color: $gray-60; +} + +.arrow-left-icon-dark { + color: $dark-secondary; +} diff --git a/packages/components/src/mobile/bottom-sheet/navigation-header.native.js b/packages/components/src/mobile/bottom-sheet/navigation-header.native.js deleted file mode 100644 index d0a28e2482c408..00000000000000 --- a/packages/components/src/mobile/bottom-sheet/navigation-header.native.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * External dependencies - */ -import { View, TouchableWithoutFeedback, Text, Platform } from 'react-native'; - -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { check, Icon, arrowLeft, close } from '@wordpress/icons'; -import { usePreferredColorSchemeStyle } from '@wordpress/compose'; - -/** - * Internal dependencies - */ -import styles from './styles.scss'; -import chevronBack from './chevron-back'; - -function BottomSheetNavigationHeader( { - leftButtonText, - leftButtonOnPress, - screen, - applyButtonOnPress, - isFullscreen, -} ) { - const isIOS = Platform.OS === 'ios'; - - const bottomSheetHeaderTitleStyle = usePreferredColorSchemeStyle( - styles.bottomSheetHeaderTitle, - styles.bottomSheetHeaderTitleDark - ); - const bottomSheetButtonTextStyle = usePreferredColorSchemeStyle( - styles.bottomSheetButtonText, - styles.bottomSheetButtonTextDark - ); - const chevronLeftStyle = usePreferredColorSchemeStyle( - styles.chevronLeftIcon, - styles.chevronLeftIconDark - ); - const arrowLeftStyle = usePreferredColorSchemeStyle( - styles.arrowLeftIcon, - styles.arrowLeftIconDark - ); - const applyButtonStyle = usePreferredColorSchemeStyle( - styles.applyButton, - styles.applyButtonDark - ); - - const renderBackButton = () => { - let backIcon; - let backText; - - if ( isIOS ) { - backIcon = isFullscreen ? undefined : ( - <Icon - icon={ chevronBack } - size={ 21 } - style={ chevronLeftStyle } - /> - ); - if ( leftButtonText ) { - backText = leftButtonText; - } else if ( isFullscreen ) { - backText = __( 'Cancel' ); - } else { - backText = __( 'Back' ); - } - } else { - backIcon = ( - <Icon - icon={ isFullscreen ? close : arrowLeft } - size={ 24 } - style={ arrowLeftStyle } - /> - ); - } - - return ( - <TouchableWithoutFeedback - onPress={ leftButtonOnPress } - accessibilityRole={ 'button' } - accessibilityLabel={ __( 'Go back' ) } - accessibilityHint={ __( - 'Navigates to the previous content sheet' - ) } - > - <View style={ styles.bottomSheetActionButton }> - <> - { backIcon } - { backText && ( - <Text - style={ bottomSheetButtonTextStyle } - maxFontSizeMultiplier={ 2 } - > - { backText } - </Text> - ) } - </> - </View> - </TouchableWithoutFeedback> - ); - }; - - return ( - <View style={ styles.bottomSheetHeader }> - <View style={ styles.bottomSheetHeaderLeft }> - { renderBackButton() } - </View> - <Text - accessibilityRole="header" - style={ bottomSheetHeaderTitleStyle } - maxFontSizeMultiplier={ 3 } - > - { screen } - </Text> - <View style={ styles.bottomSheetHeaderRight }> - { !! applyButtonOnPress && ( - <TouchableWithoutFeedback - onPress={ applyButtonOnPress } - accessibilityRole={ 'button' } - accessibilityLabel={ __( 'Apply' ) } - accessibilityHint={ __( 'Applies the setting' ) } - > - <View style={ styles.bottomSheetActionButton }> - { isIOS ? ( - <Text - style={ bottomSheetButtonTextStyle } - maxFontSizeMultiplier={ 2 } - > - { __( 'Apply' ) } - </Text> - ) : ( - <Icon - icon={ check } - size={ 24 } - style={ applyButtonStyle } - /> - ) } - </View> - </TouchableWithoutFeedback> - ) } - </View> - </View> - ); -} - -export default BottomSheetNavigationHeader; diff --git a/packages/components/src/mobile/bottom-sheet/sub-sheet/README.md b/packages/components/src/mobile/bottom-sheet/sub-sheet/README.md index 592039a8a5b140..1e60bb74ac5347 100644 --- a/packages/components/src/mobile/bottom-sheet/sub-sheet/README.md +++ b/packages/components/src/mobile/bottom-sheet/sub-sheet/README.md @@ -49,10 +49,10 @@ const ExampleControl = () => { showSheet={ showSubSheet } > <> - <BottomSheet.NavigationHeader - screen={ 'Howdy' } - leftButtonOnPress={ goBack } - /> + <BottomSheet.NavBar> + <BottomSheet.NavBar.BackButton onPress={ goBack } /> + <BottomSheet.NavBar.Heading>{ 'Howdy' }</BottomSheet.NavBar.Heading> + </BottomSheet.NavBar> <View paddingHorizontal={ 16 }> <Text>{ 'World' }</Text> </View> diff --git a/packages/components/src/mobile/color-settings/gradient-picker-screen.native.js b/packages/components/src/mobile/color-settings/gradient-picker-screen.native.js index 74d4e9d30bda50..c94d9e37a17ec6 100644 --- a/packages/components/src/mobile/color-settings/gradient-picker-screen.native.js +++ b/packages/components/src/mobile/color-settings/gradient-picker-screen.native.js @@ -13,7 +13,7 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import CustomGradientPicker from '../../custom-gradient-picker'; -import NavigationHeader from '../bottom-sheet/navigation-header'; +import NavBar from '../bottom-sheet/nav-bar'; const GradientPickerScreen = () => { const navigation = useNavigation(); @@ -21,10 +21,10 @@ const GradientPickerScreen = () => { const { setColor, currentValue, isGradientColor } = route.params; return ( <View> - <NavigationHeader - screen={ __( 'Customize Gradient' ) } - leftButtonOnPress={ navigation.goBack } - /> + <NavBar> + <NavBar.BackButton onPress={ navigation.goBack } /> + <NavBar.Heading>{ __( 'Customize Gradient' ) }</NavBar.Heading> + </NavBar> <CustomGradientPicker setColor={ setColor } currentValue={ currentValue } diff --git a/packages/components/src/mobile/color-settings/palette.screen.native.js b/packages/components/src/mobile/color-settings/palette.screen.native.js index 33456ee03a4fd6..f606b407b88326 100644 --- a/packages/components/src/mobile/color-settings/palette.screen.native.js +++ b/packages/components/src/mobile/color-settings/palette.screen.native.js @@ -20,7 +20,7 @@ import { useRoute, useNavigation } from '@react-navigation/native'; */ import ColorPalette from '../../color-palette'; import ColorIndicator from '../../color-indicator'; -import NavigationHeader from '../bottom-sheet/navigation-header'; +import NavBar from '../bottom-sheet/nav-bar'; import SegmentedControls from '../segmented-control'; import { colorsUtils } from './utils'; @@ -164,10 +164,10 @@ const PaletteScreen = () => { } return ( <View> - <NavigationHeader - screen={ label } - leftButtonOnPress={ navigation.goBack } - /> + <NavBar> + <NavBar.BackButton onPress={ navigation.goBack } /> + <NavBar.Heading>{ label } </NavBar.Heading> + </NavBar> <ColorPalette setColor={ setColor } activeColor={ currentValue } diff --git a/packages/components/src/mobile/focal-point-settings-panel/index.native.js b/packages/components/src/mobile/focal-point-settings-panel/index.native.js index 6b294526f1b0db..879f6c2c4e7d6b 100644 --- a/packages/components/src/mobile/focal-point-settings-panel/index.native.js +++ b/packages/components/src/mobile/focal-point-settings-panel/index.native.js @@ -14,7 +14,7 @@ import { BottomSheetContext, FocalPointPicker } from '@wordpress/components'; /** * Internal dependencies */ -import NavigationHeader from '../bottom-sheet/navigation-header'; +import NavBar from '../bottom-sheet/nav-bar'; import styles from './styles.scss'; const FocalPointSettingsPanelMemo = memo( @@ -43,12 +43,17 @@ const FocalPointSettingsPanelMemo = memo( return ( <SafeAreaView style={ styles.safearea }> - <NavigationHeader - screen={ __( 'Edit focal point' ) } - leftButtonOnPress={ () => onButtonPress( 'cancel' ) } - applyButtonOnPress={ () => onButtonPress( 'apply' ) } - isFullscreen - /> + <NavBar> + <NavBar.DismissButton + onPress={ () => onButtonPress( 'cancel' ) } + /> + <NavBar.Heading> + { __( 'Edit focal point' ) } + </NavBar.Heading> + <NavBar.ApplyButton + onPress={ () => onButtonPress( 'apply' ) } + /> + </NavBar> <FocalPointPicker focalPoint={ draftFocalPoint } onChange={ setPosition } diff --git a/packages/components/src/mobile/link-picker/index.native.js b/packages/components/src/mobile/link-picker/index.native.js index a007795a37450a..76797bbd26ef2a 100644 --- a/packages/components/src/mobile/link-picker/index.native.js +++ b/packages/components/src/mobile/link-picker/index.native.js @@ -18,7 +18,7 @@ import { usePreferredColorSchemeStyle } from '@wordpress/compose'; * Internal dependencies */ import LinkPickerResults from './link-picker-results'; -import NavigationHeader from '../bottom-sheet/navigation-header'; +import NavBar from '../bottom-sheet/nav-bar'; import styles from './styles.scss'; // this creates a search suggestion for adding a url directly @@ -81,12 +81,11 @@ export const LinkPicker = ( { return ( <SafeAreaView style={ styles.safeArea }> - <NavigationHeader - screen={ __( 'Link to' ) } - leftButtonOnPress={ cancel } - applyButtonOnPress={ onSubmit } - isFullscreen - /> + <NavBar> + <NavBar.DismissButton onPress={ cancel } /> + <NavBar.Heading>{ __( 'Link to' ) }</NavBar.Heading> + <NavBar.ApplyButton onPress={ onSubmit } /> + </NavBar> <View style={ styles.contentContainer }> <BottomSheet.Cell icon={ link } diff --git a/packages/editor/src/components/editor-help/help-detail-navigation-screen.native.js b/packages/editor/src/components/editor-help/help-detail-navigation-screen.native.js index 2240dd4e64d28a..f238cf006e6d34 100644 --- a/packages/editor/src/components/editor-help/help-detail-navigation-screen.native.js +++ b/packages/editor/src/components/editor-help/help-detail-navigation-screen.native.js @@ -31,10 +31,14 @@ const HelpDetailNavigationScreen = ( { content, label } ) => { return ( <BottomSheet.NavigationScreen isScrollable fullScreen> <View style={ styles.container }> - <BottomSheet.NavigationHeader - screen={ label } - leftButtonOnPress={ navigation.goBack } - /> + <BottomSheet.NavBar> + <BottomSheet.NavBar.BackButton + onPress={ navigation.goBack } + /> + <BottomSheet.NavBar.Heading> + { label } + </BottomSheet.NavBar.Heading> + </BottomSheet.NavBar> <ScrollView { ...listProps } contentContainerStyle={ { diff --git a/packages/editor/src/components/editor-help/index.native.js b/packages/editor/src/components/editor-help/index.native.js index bcef707a0e046e..b9eaaf4ccb254b 100644 --- a/packages/editor/src/components/editor-help/index.native.js +++ b/packages/editor/src/components/editor-help/index.native.js @@ -61,6 +61,11 @@ function EditorHelpTopics( { close, isVisible, onClose } ) { postType: select( editorStore ).getEditedPostAttribute( 'type' ), } ) ); + const title = + postType === 'page' + ? __( 'How to edit your page' ) + : __( 'How to edit your post' ); + return ( <BottomSheet isVisible={ isVisible } @@ -76,16 +81,15 @@ function EditorHelpTopics( { close, isVisible, onClose } ) { name="help-topics" > <View style={ styles.container }> - <BottomSheet.NavigationHeader - isFullscreen - leftButtonOnPress={ close } - leftButtonText={ __( 'Close' ) } - screen={ - postType === 'page' - ? __( 'How to edit your page' ) - : __( 'How to edit your post' ) - } - /> + <BottomSheet.NavBar> + <BottomSheet.NavBar.DismissButton + onPress={ close } + iosText={ __( 'Close' ) } + /> + <BottomSheet.NavBar.Heading> + { title } + </BottomSheet.NavBar.Heading> + </BottomSheet.NavBar> <BottomSheetConsumer> { ( { listProps } ) => { const contentContainerStyle = StyleSheet.flatten( From dd5fd6b2466d33177d46dcdc0733abd0217e044a Mon Sep 17 00:00:00 2001 From: Nik Tsekouras <ntsekouras@outlook.com> Date: Tue, 7 Sep 2021 20:57:24 +0300 Subject: [PATCH 198/214] [Block Library - Social Links]: Use the new `flex` layout (#34493) * [Block Library - Social Links]: Use the new `flex` layout * don't check for theme support in social links edit fn * associate `themeSupportsLayout` with `default/flow` layout * handle deprecations * add fixture and fix deprecation handling for default value * Remove link margin. * remove help from control * make justification work properly with JustifyContentControl - temp commit * add `toolBarControls` to layouts * fix import * rename layout.edit to layout.inspectorControls * pass layoutBlockSupport to edit functions Co-authored-by: jasmussen <joen@automattic.com> --- lib/block-supports/layout.php | 17 ++- packages/block-editor/src/hooks/layout.js | 89 ++++++++----- packages/block-editor/src/layouts/flex.js | 117 ++++++++++++++++-- packages/block-editor/src/layouts/flow.js | 19 +-- .../block-library/src/social-links/block.json | 9 +- .../src/social-links/deprecated.js | 94 ++++++++++++++ .../block-library/src/social-links/edit.js | 37 ++---- .../block-library/src/social-links/save.js | 8 +- .../block-library/src/social-links/style.scss | 8 -- .../core__social-links__deprecated-1.html | 7 ++ .../core__social-links__deprecated-1.json | 55 ++++++++ ...re__social-links__deprecated-1.parsed.json | 54 ++++++++ ...social-links__deprecated-1.serialized.html | 7 ++ 13 files changed, 427 insertions(+), 94 deletions(-) create mode 100644 test/integration/fixtures/blocks/core__social-links__deprecated-1.html create mode 100644 test/integration/fixtures/blocks/core__social-links__deprecated-1.json create mode 100644 test/integration/fixtures/blocks/core__social-links__deprecated-1.parsed.json create mode 100644 test/integration/fixtures/blocks/core__social-links__deprecated-1.serialized.html diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 71fb0ac6817ea5..7b43e37a3cc8bf 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -29,7 +29,7 @@ function gutenberg_register_layout_support( $block_type ) { * Generates the CSS corresponding to the provided layout. * * @param string $selector CSS selector. - * @param array $layout Layout object. + * @param array $layout Layout object. The one that is passed has already checked the existance of default block layout. * @param boolean $has_block_gap_support Whether the theme has support for the block gap. * * @return string CSS style. @@ -68,6 +68,13 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support $style .= "$selector > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }"; } } elseif ( 'flex' === $layout_type ) { + $justify_content_options = array( + 'left' => 'flex-start', + 'right' => 'flex-end', + 'center' => 'center', + 'space-between' => 'space-between', + ); + $style = "$selector {"; $style .= 'display: flex;'; if ( $has_block_gap_support ) { @@ -77,6 +84,14 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support } $style .= 'flex-wrap: wrap;'; $style .= 'align-items: center;'; + /** + * Add this style only if is not empty for backwards compatibility, + * since we intend to convert blocks that had flex layout implemented + * by custom css. + */ + if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) { + $style .= "justify-content: {$justify_content_options[ $layout['justifyContent'] ]};"; + } $style .= '}'; $style .= "$selector > * { margin: 0; }"; diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index 796de8c479a2fe..a1f17c525c0c6a 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -40,23 +40,32 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) { return getSettings().supportsLayout; }, [] ); - if ( ! themeSupportsLayout ) { - return null; - } - + const layoutBlockSupport = getBlockSupport( + blockName, + layoutBlockSupportKey, + {} + ); const { - allowSwitching: canBlockSwitchLayout, + allowSwitching, allowEditing = true, allowInheriting = true, default: defaultBlockLayout, - } = getBlockSupport( blockName, layoutBlockSupportKey ) || {}; + } = layoutBlockSupport; if ( ! allowEditing ) { return null; } - const usedLayout = layout ? layout : defaultBlockLayout || {}; + const usedLayout = layout || defaultBlockLayout || {}; const { inherit = false, type = 'default' } = usedLayout; + /** + * `themeSupportsLayout` is only relevant to the `default/flow` + * layout and it should not be taken into account when other + * `layout` types are used. + */ + if ( type === 'default' && ! themeSupportsLayout ) { + return null; + } const layoutType = getLayoutType( type ); const onChangeType = ( newType ) => @@ -65,33 +74,45 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) { setAttributes( { layout: newLayout } ); return ( - <InspectorControls> - <PanelBody title={ __( 'Layout' ) }> - { allowInheriting && !! defaultThemeLayout && ( - <ToggleControl - label={ __( 'Inherit default layout' ) } - checked={ !! inherit } - onChange={ () => - setAttributes( { layout: { inherit: ! inherit } } ) - } - /> - ) } - - { ! inherit && canBlockSwitchLayout && ( - <LayoutTypeSwitcher - type={ type } - onChange={ onChangeType } - /> - ) } - - { ! inherit && layoutType && ( - <layoutType.edit - layout={ usedLayout } - onChange={ onChangeLayout } - /> - ) } - </PanelBody> - </InspectorControls> + <> + <InspectorControls> + <PanelBody title={ __( 'Layout' ) }> + { allowInheriting && !! defaultThemeLayout && ( + <ToggleControl + label={ __( 'Inherit default layout' ) } + checked={ !! inherit } + onChange={ () => + setAttributes( { + layout: { inherit: ! inherit }, + } ) + } + /> + ) } + + { ! inherit && allowSwitching && ( + <LayoutTypeSwitcher + type={ type } + onChange={ onChangeType } + /> + ) } + + { ! inherit && layoutType && ( + <layoutType.inspectorControls + layout={ usedLayout } + onChange={ onChangeLayout } + layoutBlockSupport={ layoutBlockSupport } + /> + ) } + </PanelBody> + </InspectorControls> + { ! inherit && layoutType && ( + <layoutType.toolBarControls + layout={ usedLayout } + onChange={ onChangeLayout } + layoutBlockSupport={ layoutBlockSupport } + /> + ) } + </> ); } diff --git a/packages/block-editor/src/layouts/flex.js b/packages/block-editor/src/layouts/flex.js index 268674ee3e503e..2c1b9f69be4a56 100644 --- a/packages/block-editor/src/layouts/flex.js +++ b/packages/block-editor/src/layouts/flex.js @@ -1,27 +1,63 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; +import { + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, +} from '@wordpress/components'; /** * Internal dependencies */ import { appendSelectors } from './utils'; import useSetting from '../components/use-setting'; +import { BlockControls, JustifyContentControl } from '../components'; + +const justifyContentMap = { + left: 'flex-start', + right: 'flex-end', + center: 'center', + 'space-between': 'space-between', +}; export default { name: 'flex', - label: __( 'Flex' ), - - edit() { - return null; + inspectorControls: function FlexLayoutInspectorControls( { + layout = {}, + onChange, + } ) { + return ( + <FlexLayoutJustifyContentControl + layout={ layout } + onChange={ onChange } + /> + ); }, - - save: function FlexLayoutStyle( { selector } ) { + toolBarControls: function FlexLayoutToolbarControls( { + layout = {}, + onChange, + layoutBlockSupport, + } ) { + if ( layoutBlockSupport?.allowSwitching ) { + return null; + } + return ( + <BlockControls group="block" __experimentalExposeToChildren> + <FlexLayoutJustifyContentControl + layout={ layout } + onChange={ onChange } + isToolbar + /> + </BlockControls> + ); + }, + save: function FlexLayoutStyle( { selector, layout } ) { const blockGapSupport = useSetting( 'spacing.blockGap' ); const hasBlockGapStylesSupport = blockGapSupport !== null; - + const justifyContent = + justifyContentMap[ layout.justifyContent ] || 'flex-start'; return ( <style>{ ` ${ appendSelectors( selector ) } { @@ -33,6 +69,8 @@ export default { }; flex-wrap: wrap; align-items: center; + flex-direction: row; + justify-content: ${ justifyContent }; } ${ appendSelectors( selector, '> *' ) } { @@ -41,12 +79,71 @@ export default { ` }</style> ); }, - getOrientation() { return 'horizontal'; }, - getAlignments() { return []; }, }; + +function FlexLayoutJustifyContentControl( { + layout, + onChange, + isToolbar = false, +} ) { + const { justifyContent = 'left' } = layout; + if ( isToolbar ) { + return ( + <JustifyContentControl + allowedControls={ [ + 'left', + 'center', + 'right', + 'space-between', + ] } + value={ justifyContent } + onChange={ ( value ) => { + onChange( { + ...layout, + justifyContent: value, + } ); + } } + popoverProps={ { + position: 'bottom right', + isAlternate: true, + } } + /> + ); + } + return ( + <ToggleGroupControl + label={ __( 'Justify content' ) } + value={ justifyContent } + onChange={ ( value ) => { + onChange( { + ...layout, + justifyContent: value, + } ); + } } + isBlock + > + <ToggleGroupControlOption + value="left" + label={ _x( 'Left', 'Justify content option' ) } + /> + <ToggleGroupControlOption + value="center" + label={ _x( 'Center', 'Justify content option' ) } + /> + <ToggleGroupControlOption + value="right" + label={ _x( 'Right', 'Justify content option' ) } + /> + <ToggleGroupControlOption + value="space-between" + label={ _x( 'Space between', 'Justify content option' ) } + /> + </ToggleGroupControl> + ); +} diff --git a/packages/block-editor/src/layouts/flow.js b/packages/block-editor/src/layouts/flow.js index 1824b5120e84fc..796dad1d78a18f 100644 --- a/packages/block-editor/src/layouts/flow.js +++ b/packages/block-editor/src/layouts/flow.js @@ -17,10 +17,11 @@ import { appendSelectors } from './utils'; export default { name: 'default', - label: __( 'Flow' ), - - edit: function LayoutDefaultEdit( { layout, onChange } ) { + inspectorControls: function DefaultLayoutInspectorControls( { + layout, + onChange, + } ) { const { wideSize, contentSize } = layout; const units = useCustomUnits( { availableUnits: useSetting( 'spacing.units' ) || [ @@ -101,7 +102,9 @@ export default { </> ); }, - + toolBarControls: function DefaultLayoutToolbarControls() { + return null; + }, save: function DefaultLayoutStyle( { selector, layout = {} } ) { const { contentSize, wideSize } = layout; const blockGapSupport = useSetting( 'spacing.blockGap' ); @@ -115,11 +118,11 @@ export default { margin-left: auto !important; margin-right: auto !important; } - + ${ appendSelectors( selector, '> [data-align="wide"]' ) } { max-width: ${ wideSize ?? contentSize }; } - + ${ appendSelectors( selector, '> [data-align="full"]' ) } { max-width: none; } @@ -131,7 +134,7 @@ export default { float: left; margin-right: 2em; } - + ${ appendSelectors( selector, '> [data-align="right"]' ) } { float: right; margin-left: 2em; @@ -150,11 +153,9 @@ export default { return <style>{ style }</style>; }, - getOrientation() { return 'vertical'; }, - getAlignments( layout ) { if ( layout.alignments !== undefined ) { return layout.alignments; diff --git a/packages/block-library/src/social-links/block.json b/packages/block-library/src/social-links/block.json index 73ac6142c7042e..b9c8eddbdc4d6f 100644 --- a/packages/block-library/src/social-links/block.json +++ b/packages/block-library/src/social-links/block.json @@ -41,7 +41,14 @@ "supports": { "align": [ "left", "center", "right" ], "anchor": true, - "__experimentalExposeControlsToChildren": true + "__experimentalExposeControlsToChildren": true, + "__experimentalLayout": { + "allowSwitching": false, + "allowInheriting": false, + "default": { + "type": "flex" + } + } }, "styles": [ { "name": "default", "label": "Default", "isDefault": true }, diff --git a/packages/block-library/src/social-links/deprecated.js b/packages/block-library/src/social-links/deprecated.js index e8615282201f56..8f1ae2c42a68eb 100644 --- a/packages/block-library/src/social-links/deprecated.js +++ b/packages/block-library/src/social-links/deprecated.js @@ -8,8 +8,101 @@ import classNames from 'classnames'; */ import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; +/** + * The specific handling by `className` below is needed because `itemsJustification` + * was introduced in https://github.com/WordPress/gutenberg/pull/28980/files and wasn't + * declared in block.json. + * + * @param {Object} attributes Block's attributes. + */ +const migrateWithLayout = ( attributes ) => { + if ( !! attributes.layout ) { + return attributes; + } + const { className } = attributes; + // Matches classes with `items-justified-` prefix. + const prefix = `items-justified-`; + const justifiedItemsRegex = new RegExp( `\\b${ prefix }[^ ]*[ ]?\\b`, 'g' ); + const newAttributes = { + ...attributes, + className: className?.replace( justifiedItemsRegex, '' ).trim(), + }; + /** + * Add `layout` prop only if `justifyContent` is defined, for backwards + * compatibility. In other cases the block's default layout will be used. + * Also noting that due to the missing attribute, it's possible for a block + * to have more than one of `justified` classes. + */ + const justifyContent = className + ?.match( justifiedItemsRegex )?.[ 0 ] + ?.trim(); + if ( justifyContent ) { + Object.assign( newAttributes, { + layout: { + type: 'flex', + justifyContent: justifyContent.slice( prefix.length ), + }, + } ); + } + return newAttributes; +}; + // Social Links block deprecations. const deprecated = [ + // Implement `flex` layout. + { + attributes: { + iconColor: { + type: 'string', + }, + customIconColor: { + type: 'string', + }, + iconColorValue: { + type: 'string', + }, + iconBackgroundColor: { + type: 'string', + }, + customIconBackgroundColor: { + type: 'string', + }, + iconBackgroundColorValue: { + type: 'string', + }, + openInNewTab: { + type: 'boolean', + default: false, + }, + size: { + type: 'string', + }, + }, + isEligible: ( { layout } ) => ! layout, + migrate: migrateWithLayout, + save( props ) { + const { + attributes: { + iconBackgroundColorValue, + iconColorValue, + itemsJustification, + size, + }, + } = props; + + const className = classNames( size, { + 'has-icon-color': iconColorValue, + 'has-icon-background-color': iconBackgroundColorValue, + [ `items-justified-${ itemsJustification }` ]: itemsJustification, + } ); + + return ( + <ul { ...useBlockProps.save( { className } ) }> + <InnerBlocks.Content /> + </ul> + ); + }, + }, // V1. Remove CSS variable use for colors. { attributes: { @@ -46,6 +139,7 @@ const deprecated = [ align: [ 'left', 'center', 'right' ], anchor: true, }, + migrate: migrateWithLayout, save: ( props ) => { const { attributes: { diff --git a/packages/block-library/src/social-links/edit.js b/packages/block-library/src/social-links/edit.js index cd4a10b171adb5..3b3214be223ff0 100644 --- a/packages/block-library/src/social-links/edit.js +++ b/packages/block-library/src/social-links/edit.js @@ -6,15 +6,13 @@ import classNames from 'classnames'; /** * WordPress dependencies */ - +import { getBlockSupport } from '@wordpress/blocks'; import { Fragment, useEffect } from '@wordpress/element'; - import { BlockControls, __experimentalUseInnerBlocksProps as useInnerBlocksProps, useBlockProps, InspectorControls, - JustifyContentControl, ContrastChecker, PanelColorSettings, withColors, @@ -38,8 +36,17 @@ const sizeOptions = [ { name: __( 'Huge' ), value: 'has-huge-icon-size' }, ]; +const getDefaultBlockLayout = ( blockTypeOrName ) => { + const layoutBlockSupportConfig = getBlockSupport( + blockTypeOrName, + '__experimentalLayout' + ); + return layoutBlockSupportConfig?.default; +}; + export function SocialLinksEdit( props ) { const { + name, attributes, iconBackgroundColor, iconColor, @@ -52,10 +59,11 @@ export function SocialLinksEdit( props ) { const { iconBackgroundColorValue, iconColorValue, - itemsJustification, openInNewTab, size, + layout, } = attributes; + const usedLayout = layout || getDefaultBlockLayout( name ); // Remove icon background color if logos only style selected. const logosOnly = @@ -93,16 +101,15 @@ export function SocialLinksEdit( props ) { 'has-icon-color': iconColor.color || iconColorValue, 'has-icon-background-color': iconBackgroundColor.color || iconBackgroundColorValue, - [ `items-justified-${ itemsJustification }` ]: itemsJustification, } ); const blockProps = useBlockProps( { className } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { allowedBlocks: ALLOWED_BLOCKS, - orientation: 'horizontal', placeholder: isSelected ? SelectedSocialPlaceholder : SocialPlaceholder, templateLock: false, __experimentalAppenderTagName: 'li', + __experimentalLayout: usedLayout, } ); const POPOVER_PROPS = { @@ -111,24 +118,6 @@ export function SocialLinksEdit( props ) { return ( <Fragment> - <BlockControls group="block" __experimentalExposeToChildren> - <JustifyContentControl - allowedControls={ [ - 'left', - 'center', - 'right', - 'space-between', - ] } - value={ itemsJustification } - onChange={ ( value ) => - setAttributes( { itemsJustification: value } ) - } - popoverProps={ { - position: 'bottom right', - isAlternate: true, - } } - /> - </BlockControls> <BlockControls group="other"> <ToolbarDropdownMenu label={ __( 'Size' ) } diff --git a/packages/block-library/src/social-links/save.js b/packages/block-library/src/social-links/save.js index bbf8edebb7b1a5..7ed90959cc9cd5 100644 --- a/packages/block-library/src/social-links/save.js +++ b/packages/block-library/src/social-links/save.js @@ -10,18 +10,12 @@ import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; export default function save( props ) { const { - attributes: { - iconBackgroundColorValue, - iconColorValue, - itemsJustification, - size, - }, + attributes: { iconBackgroundColorValue, iconColorValue, size }, } = props; const className = classNames( size, { 'has-icon-color': iconColorValue, 'has-icon-background-color': iconBackgroundColorValue, - [ `items-justified-${ itemsJustification }` ]: itemsJustification, } ); return ( diff --git a/packages/block-library/src/social-links/style.scss b/packages/block-library/src/social-links/style.scss index eee1000a913f24..1e155f7f3c8e28 100644 --- a/packages/block-library/src/social-links/style.scss +++ b/packages/block-library/src/social-links/style.scss @@ -1,6 +1,4 @@ .wp-block-social-links { - display: flex; - flex-wrap: wrap; padding-left: 0; padding-right: 0; // Some themes set text-indent on all <ul> @@ -16,13 +14,7 @@ box-shadow: none; } - // Vertically balance the margin of each icon. .wp-social-link { - // This needs specificity to override some themes. - &.wp-social-link.wp-social-link { - margin: 4px 8px 4px 0; - } - // By setting the font size, we can scale icons and paddings consistently based on that. // This also allows themes to override this, if need be. a { diff --git a/test/integration/fixtures/blocks/core__social-links__deprecated-1.html b/test/integration/fixtures/blocks/core__social-links__deprecated-1.html new file mode 100644 index 00000000000000..b18602d8e8c140 --- /dev/null +++ b/test/integration/fixtures/blocks/core__social-links__deprecated-1.html @@ -0,0 +1,7 @@ +<!-- wp:social-links {"customIconColor":"#ffffff","iconColorValue":"#ffffff","customIconBackgroundColor":"#3962e3","iconBackgroundColorValue":"#3962e3","className":"has-icon-color"} --> +<ul class="wp-block-social-links has-icon-color has-icon-background-color items-justified-space-between"> + <!-- wp:social-link {"url":"https://wordpress.org","service":"wordpress"} /--> + <!-- wp:social-link {"url":"#","service":"chain"} /--> + <!-- wp:social-link {"url":"#","service":"mail"} /--> +</ul> +<!-- /wp:social-links --> diff --git a/test/integration/fixtures/blocks/core__social-links__deprecated-1.json b/test/integration/fixtures/blocks/core__social-links__deprecated-1.json new file mode 100644 index 00000000000000..e1c9d8dd21021b --- /dev/null +++ b/test/integration/fixtures/blocks/core__social-links__deprecated-1.json @@ -0,0 +1,55 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/social-links", + "isValid": true, + "attributes": { + "customIconColor": "#ffffff", + "iconColorValue": "#ffffff", + "customIconBackgroundColor": "#3962e3", + "iconBackgroundColorValue": "#3962e3", + "openInNewTab": false, + "className": "", + "layout": { + "type": "flex", + "justifyContent": "space-between" + } + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/social-link", + "isValid": true, + "attributes": { + "url": "https://wordpress.org", + "service": "wordpress" + }, + "innerBlocks": [], + "originalContent": "" + }, + { + "clientId": "_clientId_1", + "name": "core/social-link", + "isValid": true, + "attributes": { + "url": "#", + "service": "chain" + }, + "innerBlocks": [], + "originalContent": "" + }, + { + "clientId": "_clientId_2", + "name": "core/social-link", + "isValid": true, + "attributes": { + "url": "#", + "service": "mail" + }, + "innerBlocks": [], + "originalContent": "" + } + ], + "originalContent": "<ul class=\"wp-block-social-links has-icon-color has-icon-background-color items-justified-space-between\">\n\t\n\t\n\t\n</ul>" + } +] diff --git a/test/integration/fixtures/blocks/core__social-links__deprecated-1.parsed.json b/test/integration/fixtures/blocks/core__social-links__deprecated-1.parsed.json new file mode 100644 index 00000000000000..9746fed44ab7e2 --- /dev/null +++ b/test/integration/fixtures/blocks/core__social-links__deprecated-1.parsed.json @@ -0,0 +1,54 @@ +[ + { + "blockName": "core/social-links", + "attrs": { + "customIconColor": "#ffffff", + "iconColorValue": "#ffffff", + "customIconBackgroundColor": "#3962e3", + "iconBackgroundColorValue": "#3962e3", + "className": "has-icon-color" + }, + "innerBlocks": [ + { + "blockName": "core/social-link", + "attrs": { + "url": "https://wordpress.org", + "service": "wordpress" + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": "core/social-link", + "attrs": { + "url": "#", + "service": "chain" + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": "core/social-link", + "attrs": { + "url": "#", + "service": "mail" + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } + ], + "innerHTML": "\n<ul class=\"wp-block-social-links has-icon-color has-icon-background-color items-justified-space-between\">\n\t\n\t\n\t\n</ul>\n", + "innerContent": [ + "\n<ul class=\"wp-block-social-links has-icon-color has-icon-background-color items-justified-space-between\">\n\t", + null, + "\n\t", + null, + "\n\t", + null, + "\n</ul>\n" + ] + } +] diff --git a/test/integration/fixtures/blocks/core__social-links__deprecated-1.serialized.html b/test/integration/fixtures/blocks/core__social-links__deprecated-1.serialized.html new file mode 100644 index 00000000000000..b2fa19fb120641 --- /dev/null +++ b/test/integration/fixtures/blocks/core__social-links__deprecated-1.serialized.html @@ -0,0 +1,7 @@ +<!-- wp:social-links {"customIconColor":"#ffffff","iconColorValue":"#ffffff","customIconBackgroundColor":"#3962e3","iconBackgroundColorValue":"#3962e3","className":"","layout":{"type":"flex","justifyContent":"space-between"}} --> +<ul class="wp-block-social-links has-icon-color has-icon-background-color"><!-- wp:social-link {"url":"https://wordpress.org","service":"wordpress"} /--> + +<!-- wp:social-link {"url":"#","service":"chain"} /--> + +<!-- wp:social-link {"url":"#","service":"mail"} /--></ul> +<!-- /wp:social-links --> From 211313b911d3fab04e10e2c3930eb06e495b5b67 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 8 Sep 2021 10:16:52 +1000 Subject: [PATCH 199/214] Gap block support: force gap change to cause the block to re-render (fix Safari issue) (#34567) * Gap block support: force changing gap to cause the block to be re-rendered * Add browser sniffing to guard replaceChild behind isSafari check --- packages/block-editor/src/hooks/gap.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/block-editor/src/hooks/gap.js b/packages/block-editor/src/hooks/gap.js index d20b94c2b72cd6..d29a63774fbb64 100644 --- a/packages/block-editor/src/hooks/gap.js +++ b/packages/block-editor/src/hooks/gap.js @@ -12,6 +12,7 @@ import { /** * Internal dependencies */ +import { __unstableUseBlockRef as useBlockRef } from '../components/block-list/use-block-props/use-block-refs'; import useSetting from '../components/use-setting'; import { SPACING_SUPPORT_KEY } from './dimensions'; import { cleanEmptyObject } from './utils'; @@ -79,6 +80,7 @@ export function useIsGapDisabled( { name: blockName } = {} ) { */ export function GapEdit( props ) { const { + clientId, attributes: { style }, setAttributes, } = props; @@ -93,6 +95,8 @@ export function GapEdit( props ) { ], } ); + const ref = useBlockRef( clientId ); + if ( useIsGapDisabled( props ) ) { return null; } @@ -109,6 +113,19 @@ export function GapEdit( props ) { setAttributes( { style: cleanEmptyObject( newStyle ), } ); + + // In Safari, changing the `gap` CSS value on its own will not trigger the layout + // to be recalculated / re-rendered. To force the updated gap to re-render, here + // we replace the block's node with itself. + const isSafari = + window?.navigator.userAgent && + window.navigator.userAgent.includes( 'Safari' ) && + ! window.navigator.userAgent.includes( 'Chrome ' ) && + ! window.navigator.userAgent.includes( 'Chromium ' ); + + if ( ref.current && isSafari ) { + ref.current.parentNode?.replaceChild( ref.current, ref.current ); + } }; return Platform.select( { From f20ad34487f80c0292459dfadbb0d4ef3a3189b0 Mon Sep 17 00:00:00 2001 From: Ryan Welcher <me@ryanwelcher.com> Date: Wed, 8 Sep 2021 00:04:47 -0400 Subject: [PATCH 200/214] AlignmentMatrixControl : Fix/update docs (#34624) * Update example to import correctly and use a more verbose example in the onChange prop. * Add prop details. Co-authored-by: Ryan Welcher <ryanwelcher@automattic.com> --- .../src/alignment-matrix-control/README.md | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/packages/components/src/alignment-matrix-control/README.md b/packages/components/src/alignment-matrix-control/README.md index d8470d905a1ae3..e041cb7f839209 100644 --- a/packages/components/src/alignment-matrix-control/README.md +++ b/packages/components/src/alignment-matrix-control/README.md @@ -5,14 +5,61 @@ AlignmentMatrixControl components enable adjustments to horizontal and vertical ## Usage ```jsx -import { AlignmentMatrixControl } from '@wordpress/components'; +import { __experimentalAlignmentMatrixControl as AlignmentMatrixControl } from '@wordpress/components'; import { useState } from '@wordpress/element'; const Example = () => { - const [ alignment, setAlignment ] = useState( 'center center' ); + const [alignment, setAlignment] = useState('center center'); return ( - <AlignmentMatrixControl value={ alignment } onChange={ setAlignment } /> + <AlignmentMatrixControl + value={alignment} + onChange={(newAlignment) => setAlignment(newAlignment)} + /> ); }; ``` + +## Props + +The component accepts the following props: +### className + +The class that will be added with "component-alignment-matrix-control" to the classes of the wrapper <Composite/> component. +If no className is passed only "component-alignment-matrix-control" is used. + +- Type: `String` +- Required: No + +### id + +Unique ID for the component. +- Type: `String` +- Required: No +### label + +If provided, sets the aria-label attribute of the wrapper <Composite/> component. + +- Type: `String` +- Required: No +- Default: `Alignment Matrix Control` +### defaultValue + +If provided, sets the default alignment value. +- Type: `String` +- Required: No +- Default: `center center` + +### onChange + +A function that receives the updated alignment value. + +- Type: `function` +- Required: No +- Default: `noop` +### width + +If provided, sets the width of the wrapper <Composite/> component. + - Type: `Number` + - Required: No + - Default: `92` From 5d21c213db51097f317b24a5ad2ec7f1a56e3c76 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Wed, 8 Sep 2021 13:10:15 +0800 Subject: [PATCH 201/214] Remove horizontal and vertical navigation block variations from inserter (#34614) * Remove horizontal/vertical nav block variations from inserter * update fixtures * Remove default variation * Update fixtures Co-authored-by: ntsekouras <ntsekouras@outlook.com> --- packages/block-library/src/navigation/block.json | 3 ++- packages/block-library/src/navigation/edit.js | 2 +- packages/block-library/src/navigation/variations.js | 5 ++--- .../blocks/__snapshots__/navigation.test.js.snap | 12 ++++++------ .../fixtures/blocks/core__navigation.json | 1 + .../core__navigation__deprecated.serialized.html | 2 +- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index 79da72eb86a2fc..98663a8075b235 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -8,7 +8,8 @@ "textdomain": "default", "attributes": { "orientation": { - "type": "string" + "type": "string", + "default": "horizontal" }, "textColor": { "type": "string" diff --git a/packages/block-library/src/navigation/edit.js b/packages/block-library/src/navigation/edit.js index f5d7a1c44f6563..1fa9d492ed7ce3 100644 --- a/packages/block-library/src/navigation/edit.js +++ b/packages/block-library/src/navigation/edit.js @@ -151,7 +151,7 @@ function Navigation( { }, { allowedBlocks: ALLOWED_BLOCKS, - orientation: attributes.orientation || 'horizontal', + orientation: attributes.orientation, renderAppender: ( isImmediateParentOfSelectedBlock && ! selectedBlockHasDescendants ) || diff --git a/packages/block-library/src/navigation/variations.js b/packages/block-library/src/navigation/variations.js index 307fa8c0c12048..c17813f953211e 100644 --- a/packages/block-library/src/navigation/variations.js +++ b/packages/block-library/src/navigation/variations.js @@ -6,18 +6,17 @@ import { __ } from '@wordpress/i18n'; const variations = [ { name: 'horizontal', - isDefault: true, title: __( 'Navigation (horizontal)' ), description: __( 'Links shown in a row.' ), attributes: { orientation: 'horizontal' }, - scope: [ 'inserter', 'transform' ], + scope: [ 'transform' ], }, { name: 'vertical', title: __( 'Navigation (vertical)' ), description: __( 'Links shown in a column.' ), attributes: { orientation: 'vertical' }, - scope: [ 'inserter', 'transform' ], + scope: [ 'transform' ], }, ]; diff --git a/packages/e2e-tests/specs/experiments/blocks/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/experiments/blocks/__snapshots__/navigation.test.js.snap index 40204d478b4af8..aa967c86f181d0 100644 --- a/packages/e2e-tests/specs/experiments/blocks/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/experiments/blocks/__snapshots__/navigation.test.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Navigation Creating from existing Menus allows a navigation block to be created from existing menus 1`] = ` -"<!-- wp:navigation {\\"orientation\\":\\"horizontal\\"} --> +"<!-- wp:navigation --> <!-- wp:navigation-link {\\"label\\":\\"Home\\",\\"type\\":\\"custom\\",\\"url\\":\\"http://localhost:8889/\\",\\"kind\\":\\"custom\\",\\"isTopLevelLink\\":true} /--> <!-- wp:navigation-link {\\"label\\":\\"Accusamus quo repellat illum magnam quas\\",\\"type\\":\\"page\\",\\"id\\":41,\\"url\\":\\"http://localhost:8889/?page_id=41\\",\\"kind\\":\\"post-type\\",\\"isTopLevelLink\\":true} --> @@ -36,16 +36,16 @@ exports[`Navigation Creating from existing Menus allows a navigation block to be <!-- /wp:navigation -->" `; -exports[`Navigation Creating from existing Menus creates an empty navigation block when the selected existing menu is also empty 1`] = `"<!-- wp:navigation {\\"orientation\\":\\"horizontal\\"} /-->"`; +exports[`Navigation Creating from existing Menus creates an empty navigation block when the selected existing menu is also empty 1`] = `"<!-- wp:navigation /-->"`; exports[`Navigation Creating from existing Pages allows a navigation block to be created using existing pages 1`] = ` -"<!-- wp:navigation {\\"orientation\\":\\"horizontal\\"} --> +"<!-- wp:navigation --> <!-- wp:page-list {\\"isNavigationChild\\":true} /--> <!-- /wp:navigation -->" `; exports[`Navigation allows an empty navigation block to be created and manually populated using a mixture of internal and external links 1`] = ` -"<!-- wp:navigation {\\"orientation\\":\\"horizontal\\"} --> +"<!-- wp:navigation --> <!-- wp:navigation-link {\\"label\\":\\"WP\\",\\"url\\":\\"https://wordpress.org\\",\\"kind\\":\\"custom\\",\\"isTopLevelLink\\":true} /--> <!-- wp:navigation-link {\\"label\\":\\"Contact\\",\\"type\\":\\"page\\",\\"id\\":1,\\"url\\":\\"https://this/is/a/test/search/get-in-touch\\",\\"kind\\":\\"post-type\\",\\"isTopLevelLink\\":true} /--> @@ -53,13 +53,13 @@ exports[`Navigation allows an empty navigation block to be created and manually `; exports[`Navigation allows pages to be created from the navigation block and their links added to menu 1`] = ` -"<!-- wp:navigation {\\"orientation\\":\\"horizontal\\"} --> +"<!-- wp:navigation --> <!-- wp:navigation-link {\\"label\\":\\"A really long page name that will not exist\\",\\"type\\":\\"page\\",\\"id\\":1,\\"url\\":\\"https://this/is/a/test/create/page/my-new-page\\",\\"kind\\":\\"post-type\\",\\"isTopLevelLink\\":true} /--> <!-- /wp:navigation -->" `; exports[`Navigation encodes URL when create block if needed 1`] = ` -"<!-- wp:navigation {\\"orientation\\":\\"horizontal\\"} --> +"<!-- wp:navigation --> <!-- wp:navigation-link {\\"label\\":\\"wordpress.org/шеллы\\",\\"url\\":\\"https://wordpress.org/%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\\",\\"kind\\":\\"custom\\",\\"isTopLevelLink\\":true} /--> <!-- wp:navigation-link {\\"label\\":\\"お問い合わせ\\",\\"type\\":\\"page\\",\\"id\\":1,\\"url\\":\\"https://this/is/a/test/search/%E3%81%8A%E5%95%8F%E3%81%84%E5%90%88%E3%82%8F%E3%81%9B\\",\\"kind\\":\\"post-type\\",\\"isTopLevelLink\\":true} /--> diff --git a/test/integration/fixtures/blocks/core__navigation.json b/test/integration/fixtures/blocks/core__navigation.json index 0e1e52afebd32b..f0820d51af1009 100644 --- a/test/integration/fixtures/blocks/core__navigation.json +++ b/test/integration/fixtures/blocks/core__navigation.json @@ -4,6 +4,7 @@ "name": "core/navigation", "isValid": true, "attributes": { + "orientation": "horizontal", "showSubmenuIcon": true, "isResponsive": false }, diff --git a/test/integration/fixtures/blocks/core__navigation__deprecated.serialized.html b/test/integration/fixtures/blocks/core__navigation__deprecated.serialized.html index 22bf66bf725368..27f10adb73e0fc 100644 --- a/test/integration/fixtures/blocks/core__navigation__deprecated.serialized.html +++ b/test/integration/fixtures/blocks/core__navigation__deprecated.serialized.html @@ -1,3 +1,3 @@ -<!-- wp:navigation {"orientation":"horizontal","style":{"typography":{"textTransform":"lowercase","textDecoration":"line-through","fontStyle":"italic","fontWeight":"100"}}} --> +<!-- wp:navigation {"style":{"typography":{"textTransform":"lowercase","textDecoration":"line-through","fontStyle":"italic","fontWeight":"100"}}} --> <!-- wp:navigation-link {"label":"WordPress","url":"https://www.wordpress.org/"} /--> <!-- /wp:navigation --> From e3ebc6d2a66354dcdad1fab0c04f287b52adc4a7 Mon Sep 17 00:00:00 2001 From: Staci Cooper <63313398+stacimc@users.noreply.github.com> Date: Tue, 7 Sep 2021 22:38:42 -0700 Subject: [PATCH 202/214] Border Controls: Display color indicator and check selected color (#34467) * Fix border panel color indicator * Pass color value rather than slug as prop, to fix preset color preview * Lazily initialize state --- packages/block-editor/src/hooks/border-color.js | 13 ++++++++++++- .../src/components/sidebar/border-panel.js | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/hooks/border-color.js b/packages/block-editor/src/hooks/border-color.js index 036ff345a8ccdd..9060f21236d69c 100644 --- a/packages/block-editor/src/hooks/border-color.js +++ b/packages/block-editor/src/hooks/border-color.js @@ -9,6 +9,7 @@ import classnames from 'classnames'; import { addFilter } from '@wordpress/hooks'; import { __ } from '@wordpress/i18n'; import { createHigherOrderComponent } from '@wordpress/compose'; +import { useState } from '@wordpress/element'; /** * Internal dependencies @@ -48,8 +49,18 @@ export function BorderColorEdit( props ) { const colors = useSetting( 'color.palette' ) || EMPTY_ARRAY; const disableCustomColors = ! useSetting( 'color.custom' ); const disableCustomGradients = ! useSetting( 'color.customGradient' ); + const [ colorValue, setColorValue ] = useState( + () => + getColorObjectByAttributeValues( + colors, + borderColor, + style?.border?.color + )?.color + ); const onChangeColor = ( value ) => { + setColorValue( value ); + const colorObject = getColorObjectByColorValue( colors, value ); const newStyle = { ...style, @@ -71,7 +82,7 @@ export function BorderColorEdit( props ) { return ( <ColorGradientControl label={ __( 'Color' ) } - value={ borderColor || style?.border?.color } + colorValue={ colorValue } colors={ colors } gradients={ undefined } disableCustomColors={ disableCustomColors } diff --git a/packages/edit-site/src/components/sidebar/border-panel.js b/packages/edit-site/src/components/sidebar/border-panel.js index 46f8dbad535049..19cdcff68c6eed 100644 --- a/packages/edit-site/src/components/sidebar/border-panel.js +++ b/packages/edit-site/src/components/sidebar/border-panel.js @@ -123,7 +123,7 @@ export default function BorderPanel( { { hasBorderColor && ( <ColorGradientControl label={ __( 'Color' ) } - value={ borderColor } + colorValue={ borderColor } colors={ colors } gradients={ undefined } disableCustomColors={ disableCustomColors } From e3591aa86bfc4fe02da616ef50e722504ba8b3d8 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 8 Sep 2021 16:03:44 +1000 Subject: [PATCH 203/214] Gallery block: Fix media placeholder height in site editor (#34629) --- packages/block-library/src/gallery/editor.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index 584654a814af11..47ebb1bdaa1e8c 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -45,7 +45,6 @@ figure.wp-block-gallery { } .block-editor-media-placeholder { margin: 0; - height: 100%; &::before { box-shadow: 0 0 0 $border-width $white inset, 0 0 0 3px var(--wp-admin-theme-color) inset; From 391c2442927db664359ffd6a216f99b38a27d88d Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation <gutenberg@wordpress.org> Date: Wed, 8 Sep 2021 10:03:48 +0000 Subject: [PATCH 204/214] Bump plugin version to 11.5.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- readme.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 47dd00c9a4b73f..5e8552586f6858 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the new block editor in core. * Requires at least: 5.7 * Requires PHP: 5.6 - * Version: 11.4.1 + * Version: 11.5.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index b7f4644029528d..8a72667def70e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "11.4.1", + "version": "11.5.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a09653db09c072..d17df424be1b8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "11.4.1", + "version": "11.5.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", diff --git a/readme.txt b/readme.txt index f9ffcf44f33581..ea69a0fe30bf32 100644 --- a/readme.txt +++ b/readme.txt @@ -55,4 +55,4 @@ View <a href="https://developer.wordpress.org/block-editor/principles/versions-i == Changelog == -To read the changelog for Gutenberg 11.4.1, please navigate to the <a href="https://github.com/WordPress/gutenberg/releases/tag/v11.4.1">release page</a>. +To read the changelog for Gutenberg 11.5.0-rc.1, please navigate to the <a href="https://github.com/WordPress/gutenberg/releases/tag/v11.5.0-rc.1">release page</a>. From 83af4c05a7c93f6df3a172412e2993fe27c20741 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation <gutenberg@wordpress.org> Date: Wed, 8 Sep 2021 10:26:16 +0000 Subject: [PATCH 205/214] Update Changelog for 11.5.0-rc.1 --- changelog.txt | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) diff --git a/changelog.txt b/changelog.txt index d8694890cee2bc..ffc9af781783fa 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,286 @@ == Changelog == += 11.5.0-rc.1 = + +### Features + +- Add Dark Mode-specific help section images. ([34361](https://github.com/WordPress/gutenberg/pull/34361)) + +#### Block Library +- Group block: Add a row variation. ([34535](https://github.com/WordPress/gutenberg/pull/34535)) + +#### Design Tools +- Block Support: Add gap block support feature. ([33991](https://github.com/WordPress/gutenberg/pull/33991)) + + +### Enhancements + +- Add isRawAttribute to entity configuration. ([34388](https://github.com/WordPress/gutenberg/pull/34388)) +- Consolidate the PATHS_WITH_MERGE constant to one instance. ([34407](https://github.com/WordPress/gutenberg/pull/34407)) +- Fix title missing in bug report form. ([34504](https://github.com/WordPress/gutenberg/pull/34504)) +- General Interface: Make permalinks documentation URL translatable. ([34282](https://github.com/WordPress/gutenberg/pull/34282)) +- Update bug form to use drop downs. ([34458](https://github.com/WordPress/gutenberg/pull/34458)) + +#### Block Library +- Update Site Logo block description to be concise. ([34471](https://github.com/WordPress/gutenberg/pull/34471)) +- Update the Table block description to be concise. ([34475](https://github.com/WordPress/gutenberg/pull/34475)) +- Use blockGap between Columns blocks. ([34456](https://github.com/WordPress/gutenberg/pull/34456)) +- Video Block: Use existing video poster image on insert. ([34415](https://github.com/WordPress/gutenberg/pull/34415)) +- [Query Pagination Next/Previous]: Add an arrow attribute and sync next/previous block's arrow. ([33656](https://github.com/WordPress/gutenberg/pull/33656)) + +#### Design Tools +- Add wide alignment control only if theme provides `layout.wideSize`. ([34586](https://github.com/WordPress/gutenberg/pull/34586)) +- Gap block support: Force gap change to cause the block to re-render (fix Safari issue). ([34567](https://github.com/WordPress/gutenberg/pull/34567)) +- Post Author Block: Add duotone suport. ([34408](https://github.com/WordPress/gutenberg/pull/34408)) +- ToolsPanel: Change icon from horizontal to vertical ellipsis. ([34369](https://github.com/WordPress/gutenberg/pull/34369)) + +#### Navigation Screen +- Add undo redo buttons in navigation editor. ([34533](https://github.com/WordPress/gutenberg/pull/34533)) +- Disable "block-nav-menus" feature for the purposes of removing the "experimental" status on the Navigation Editor. ([34444](https://github.com/WordPress/gutenberg/pull/34444)) +- Preload menu REST API requests on new navigation editor. ([34364](https://github.com/WordPress/gutenberg/pull/34364)) +- Update navigation editor placeholder. ([34568](https://github.com/WordPress/gutenberg/pull/34568)) + +#### Widgets Editor +- Add 'Widget Group' block to widgets screens. ([34484](https://github.com/WordPress/gutenberg/pull/34484)) +- Legacy widget rendering endpoint. ([34230](https://github.com/WordPress/gutenberg/pull/34230)) + +#### Global Styles +- Allow disabling `text` and `background` color via `theme.json`. ([34420](https://github.com/WordPress/gutenberg/pull/34420)) +- Make global styles available to all themes. ([34334](https://github.com/WordPress/gutenberg/pull/34334)) + +#### Block Editor +- Media Placeholder: Change media URL input type to allow a local URL path. ([29138](https://github.com/WordPress/gutenberg/pull/29138)) + +#### Block Variations +- Remove horizontal and vertical navigation block variations from inserter. ([34614](https://github.com/WordPress/gutenberg/pull/34614)) + +#### Post Editor +- Try: Title block gap. ([34570](https://github.com/WordPress/gutenberg/pull/34570)) + +#### Themes +- Add default editor styles applied to themes without theme.json and without editor styles. ([34439](https://github.com/WordPress/gutenberg/pull/34439)) + +#### Components +- MenuItem: Add right padding for unchecked radio and checkbox items. ([34406](https://github.com/WordPress/gutenberg/pull/34406)) + +#### Full Site Editing +- Limit FSE admin notices to the Themes screen. ([34353](https://github.com/WordPress/gutenberg/pull/34353)) + +#### List View +- Block Navigation List: Do not show appender and avoid closing the modal on block select. ([34337](https://github.com/WordPress/gutenberg/pull/34337)) + +#### CSS & Styling +- Block Styles: Fix long strings of text without spaces overflow the block. ([34222](https://github.com/WordPress/gutenberg/pull/34222)) + +#### Testing +- Debug e2e-tests in vscode. ([29788](https://github.com/WordPress/gutenberg/pull/29788)) + + +### New APIs + +#### Design Tools +- Allow themes with theme.json to opt-out of block gap styles. ([34491](https://github.com/WordPress/gutenberg/pull/34491)) + + +### Bug Fixes + +- Block Toolbar & Popover component - Prevent sticky position from causing permanently obscured areas of the selected block. ([33981](https://github.com/WordPress/gutenberg/pull/33981)) +- Core Data: Adds 'include' to the query key. ([34583](https://github.com/WordPress/gutenberg/pull/34583)) +- ESLint: Add useSelect to direct function calls list. ([34301](https://github.com/WordPress/gutenberg/pull/34301)) +- Fix menu item padding regression. ([34435](https://github.com/WordPress/gutenberg/pull/34435)) +- Fix text-menu min widths. ([34532](https://github.com/WordPress/gutenberg/pull/34532)) +- Scripts: Only use svgr/webpack in js files. ([34394](https://github.com/WordPress/gutenberg/pull/34394)) +- Use resolveSelect instead of select in saveEntityRecord. ([34584](https://github.com/WordPress/gutenberg/pull/34584)) + +#### Block Library +- Fix Column bottom sheet Android close button. ([34332](https://github.com/WordPress/gutenberg/pull/34332)) +- Fix Page List styles inside responsive Navigation. ([34517](https://github.com/WordPress/gutenberg/pull/34517)) +- Fix navigation block classname issues. ([34344](https://github.com/WordPress/gutenberg/pull/34344)) +- Fix responsive menu height regression. ([34488](https://github.com/WordPress/gutenberg/pull/34488)) +- Fix submenu layout in navigation page list. ([34342](https://github.com/WordPress/gutenberg/pull/34342)) +- Fix undo/redo 'trap' in navigation link block. ([34565](https://github.com/WordPress/gutenberg/pull/34565)) +- Fix various React warnings in development log. ([34428](https://github.com/WordPress/gutenberg/pull/34428)) +- Gallery block: Fix bug with stalled upload when image size too large. ([34371](https://github.com/WordPress/gutenberg/pull/34371)) +- Gallery block: Fix media placeholder height in site editor. ([34629](https://github.com/WordPress/gutenberg/pull/34629)) +- Gallery block: Fix problem with overflowing captions on new gallery block format. ([34402](https://github.com/WordPress/gutenberg/pull/34402)) +- Site title: Allow empty title in edit mode. ([34274](https://github.com/WordPress/gutenberg/pull/34274)) +- Try: Fix so submenus only take up space when visible. ([34382](https://github.com/WordPress/gutenberg/pull/34382)) +- Video Block: Fix TypeError when removing poster. ([34411](https://github.com/WordPress/gutenberg/pull/34411)) + +#### Components +- Align labels on focal point picker position controls above the inputs. ([34209](https://github.com/WordPress/gutenberg/pull/34209)) +- Check if in browser env before calling `CSS.supports`. ([34572](https://github.com/WordPress/gutenberg/pull/34572)) +- CustomSelectControl: Add describedBy fallback. ([34385](https://github.com/WordPress/gutenberg/pull/34385)) +- DateTime Component: Fix sizing of help info. ([34370](https://github.com/WordPress/gutenberg/pull/34370)) +- Fix `ToggleGroupControlBackdrop` not updating size when `isAdaptiveWidth` prop changes. ([34595](https://github.com/WordPress/gutenberg/pull/34595)) +- Fix selected value computation in `CustomSelectControl` when no initial `value` is set. ([34490](https://github.com/WordPress/gutenberg/pull/34490)) +- Fix subheadings from wrapping. ([34319](https://github.com/WordPress/gutenberg/pull/34319)) + +#### Design Tools +- Border Controls: Display color indicator and check selected color. ([34467](https://github.com/WordPress/gutenberg/pull/34467)) +- Border Support: Fix check for displaying border support panel. ([34516](https://github.com/WordPress/gutenberg/pull/34516)) +- Letter Spacing: Group letter spacing correctly under typography supports. ([34515](https://github.com/WordPress/gutenberg/pull/34515)) + +#### Widgets Editor +- Fix Block Settings sidebar unexpectedly collapsing. ([34543](https://github.com/WordPress/gutenberg/pull/34543)) +- Legacy widget's preview functionality is broken when the page is moved. ([34384](https://github.com/WordPress/gutenberg/pull/34384)) + +#### Global Styles +- Fix block-level global styles color panels. ([34293](https://github.com/WordPress/gutenberg/pull/34293)) +- Font Appearance Control: Fix error in global styles for Site Title in TT1-Blocks. ([34520](https://github.com/WordPress/gutenberg/pull/34520)) + +#### Testing +- Jest Preset: Restore the default setting for the `verbose` option. ([34327](https://github.com/WordPress/gutenberg/pull/34327)) +- Make Test_Widget compatible with WP_Widget. ([34355](https://github.com/WordPress/gutenberg/pull/34355)) + +#### Meta Boxes +- Change default value of enableCustomFields to undefined. ([33931](https://github.com/WordPress/gutenberg/pull/33931)) +- Fix metabox reordering. ([30617](https://github.com/WordPress/gutenberg/pull/30617)) + +#### Block API +- Blocks: Register block when invalid value provided for the icon. ([34350](https://github.com/WordPress/gutenberg/pull/34350)) + +#### Accessibility +- Fix button block focus trap after a URL has been added. ([34314](https://github.com/WordPress/gutenberg/pull/34314)) + +#### REST API +- Default batch processor: Respect the batch endpoint's maxItems. ([34280](https://github.com/WordPress/gutenberg/pull/34280)) + +#### Navigation Screen +- Decode entities in the menu names. ([34263](https://github.com/WordPress/gutenberg/pull/34263)) + +#### Rich Text +- [Block Editor]: Fix caret position on block merging. ([34169](https://github.com/WordPress/gutenberg/pull/34169)) + +#### Block Editor +- Keep id on paste if internal link points to it. ([31107](https://github.com/WordPress/gutenberg/pull/31107)) + +#### Build Tooling +- Fix build hang on Windows 10. ([23589](https://github.com/WordPress/gutenberg/pull/23589)) + + +### Performance + +- Improve the getBlock and getBlocks performance. ([34241](https://github.com/WordPress/gutenberg/pull/34241)) +- Remove duplicated `useValidAlignment` hook. ([34593](https://github.com/WordPress/gutenberg/pull/34593)) +- core-data: Move locks state from store to local variable. ([34374](https://github.com/WordPress/gutenberg/pull/34374)) + +#### Global Styles +- Remove colors classes from the packages that are already provided by global styles. ([34510](https://github.com/WordPress/gutenberg/pull/34510)) + + +### Experiments + +#### Block Library +- Allow Site Title and Logo inside Navigation block. ([33316](https://github.com/WordPress/gutenberg/pull/33316)) +- [Social Links]: Use the new `flex` layout. ([34493](https://github.com/WordPress/gutenberg/pull/34493)) + +#### Global Styles +- Add unit tests for edit site editor utils. ([34401](https://github.com/WordPress/gutenberg/pull/34401)) + + +### Documentation + +- Correct typo in Blocks Documentation. ([34396](https://github.com/WordPress/gutenberg/pull/34396)) +- Eslint: Add no-unsafe-wp-apis to rules list in the documentation. ([34416](https://github.com/WordPress/gutenberg/pull/34416)) +- RNMobile: Fix links, images, and formatting in documentation. ([34300](https://github.com/WordPress/gutenberg/pull/34300)) +- Replace withSelect references with useSelect. ([34549](https://github.com/WordPress/gutenberg/pull/34549)) +- Update DuotonePicker documentation for accuracy. ([34494](https://github.com/WordPress/gutenberg/pull/34494)) +- Updated Template section copy. ([34383](https://github.com/WordPress/gutenberg/pull/34383)) +- [Docs]: Update block variations documentation about `block` scope. ([34455](https://github.com/WordPress/gutenberg/pull/34455)) +- [Prettier] Updated README.md file with the correct syntax. ([34600](https://github.com/WordPress/gutenberg/pull/34600)) + +#### Components +- Fix/update documentation alignment matrix control. ([34624](https://github.com/WordPress/gutenberg/pull/34624)) + + +### Code Quality + +- Add getFilename method to the URL package. ([34313](https://github.com/WordPress/gutenberg/pull/34313)) +- Block Editor: Ensure that `blockType` is defined when accessing `apiVersion`. ([34346](https://github.com/WordPress/gutenberg/pull/34346)) +- Block Editor: Migrate `lightBlockWrapper` support to `apiVersion` for blocks. ([34459](https://github.com/WordPress/gutenberg/pull/34459)) +- Code cleanup to the getBlock refactoring. ([34326](https://github.com/WordPress/gutenberg/pull/34326)) +- Fix linting error in trunk. ([34464](https://github.com/WordPress/gutenberg/pull/34464)) +- Fix linting errors. ([34596](https://github.com/WordPress/gutenberg/pull/34596)) +- Linting: Remove global event listener warning. ([34528](https://github.com/WordPress/gutenberg/pull/34528)) +- Migrate canUser resolver to thunks. ([34580](https://github.com/WordPress/gutenberg/pull/34580)) +- Migrate entities.js to thunks. ([34582](https://github.com/WordPress/gutenberg/pull/34582)) +- Migrate getAutosaves resolver to thunks. ([34581](https://github.com/WordPress/gutenberg/pull/34581)) +- Migrate getEntityRecord resolver to thunks. ([34576](https://github.com/WordPress/gutenberg/pull/34576)) +- Migrate getEntityRecords resolver to thunks. ([34578](https://github.com/WordPress/gutenberg/pull/34578)) +- Migrate resolvers to thunks: GetAuthors,_getCurrentUser,__getCurrentTheme,__getThemeSupports. ([34579](https://github.com/WordPress/gutenberg/pull/34579)) +- Refactor deleteEntityRecord to use thunks instead of generators. ([34386](https://github.com/WordPress/gutenberg/pull/34386)) +- Refactor editEntityRecord, undo, and redo to be thunks instead of generators. ([34387](https://github.com/WordPress/gutenberg/pull/34387)) +- Refactor saveEntityRecord from redux-rungen to async thunks. ([33201](https://github.com/WordPress/gutenberg/pull/33201)) +- Remove confusing punctuation. ([34322](https://github.com/WordPress/gutenberg/pull/34322)) +- Remove extraction of raw values in saveEntityRecords. ([34502](https://github.com/WordPress/gutenberg/pull/34502)) +- Rich text: Replace global event handlers with local ones. ([34492](https://github.com/WordPress/gutenberg/pull/34492)) +- core-data: Remove the PROCESS_PENDING_LOCK_REQUESTS action. ([34343](https://github.com/WordPress/gutenberg/pull/34343)) +- i18n: Add context to 'none' strings for better translations. ([34341](https://github.com/WordPress/gutenberg/pull/34341)) +- useDropZone: Ensure drag event targets HTMLElement. ([34272](https://github.com/WordPress/gutenberg/pull/34272)) + +#### Block Library +- Button block: Replace global shortcut event handlers with local ones. ([34498](https://github.com/WordPress/gutenberg/pull/34498)) +- Gallery Block : Remove IE specific CSS hacks. ([34372](https://github.com/WordPress/gutenberg/pull/34372)) +- Gallery block: Add docblock comments to the new gallery hooks. ([34562](https://github.com/WordPress/gutenberg/pull/34562)) +- Navigation link block: Replace global shortcut event handlers with local ones. ([34500](https://github.com/WordPress/gutenberg/pull/34500)) +- Refactor navigation block to use generic classnames. ([34171](https://github.com/WordPress/gutenberg/pull/34171)) +- Remove redundant css selector. ([34277](https://github.com/WordPress/gutenberg/pull/34277)) + +#### Components +- CustomGradientBar: Replace global shortcut event handlers with local ones. ([34505](https://github.com/WordPress/gutenberg/pull/34505)) +- Guide: Replace global shortcut event handlers with local ones. ([34503](https://github.com/WordPress/gutenberg/pull/34503)) +- Navigate regions: Use React events for shortcuts (portal bubbles & contextual). ([33633](https://github.com/WordPress/gutenberg/pull/33633)) +- Rename `PolymorphicComponent*` types to `WordPressComponent*`. ([34330](https://github.com/WordPress/gutenberg/pull/34330)) +- Simplify Modal with hooks. ([34412](https://github.com/WordPress/gutenberg/pull/34412)) +- Try: Simplify & polish heading levels. ([34378](https://github.com/WordPress/gutenberg/pull/34378)) + +#### Post Editor +- Editor package: Replace hardcoded store key. ([34296](https://github.com/WordPress/gutenberg/pull/34296)) +- Fix gray W menu color. ([34318](https://github.com/WordPress/gutenberg/pull/34318)) + +#### Block Editor +- Fix Animated warning log. ([34197](https://github.com/WordPress/gutenberg/pull/34197)) +- Rich text (core): OnFocus method can be replaced with HTMLElement.focus. ([32054](https://github.com/WordPress/gutenberg/pull/32054)) + +#### CSS & Styling +- Navigation: Use gap instead of margin. ([32367](https://github.com/WordPress/gutenberg/pull/32367)) + + +### Tools + +- Added janw-me to the Codeowners for the PHP FSE folder. ([32990](https://github.com/WordPress/gutenberg/pull/32990)) + +#### Build Tooling +- ESLint: Update error message for `@emotion/css` with info about the `useCx` hook. ([34418](https://github.com/WordPress/gutenberg/pull/34418)) +- More work on the stability of the performance metrics. ([34229](https://github.com/WordPress/gutenberg/pull/34229)) + + +### Various + +- Upgrade gradle to 7.1.1 agp to 4.2.2. ([34048](https://github.com/WordPress/gutenberg/pull/34048)) + +#### Components +- Combobox Component: Only force expanded state if the input has focus. ([34090](https://github.com/WordPress/gutenberg/pull/34090)) +- Try: Vertical heading levels menu. ([32926](https://github.com/WordPress/gutenberg/pull/32926)) +- [ToggleGroupControl]: Update stories to use knobs. ([34497](https://github.com/WordPress/gutenberg/pull/34497)) + +#### Plugin +- Update the minimum supported WordPress version to 5.7. ([34536](https://github.com/WordPress/gutenberg/pull/34536)) + +#### Accessibility +- Accessibility improvement for font weight screen reader description. ([34312](https://github.com/WordPress/gutenberg/pull/34312)) + +#### Widgets Editor +- Prevent focus trap in Legacy Widget block’s preview iframe. ([33614](https://github.com/WordPress/gutenberg/pull/33614)) + +#### Post Editor +- Expose ThemeSupportCheck component. ([20506](https://github.com/WordPress/gutenberg/pull/20506)) + + + + + = 11.4.1 = ### Bug Fixes From fea6b6b78b60630258e40f8758fe7e34ed42f7b4 Mon Sep 17 00:00:00 2001 From: Dave Smith <getdavemail@gmail.com> Date: Wed, 8 Sep 2021 11:30:19 +0100 Subject: [PATCH 206/214] Only capture toolbars on parent Nav block when not in vertical mode (#34615) * Only capture toolbars on parent Nav block when not in vertical mode * Add code comment for context and clarity about reason for change --- packages/block-library/src/navigation/edit.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation/edit.js b/packages/block-library/src/navigation/edit.js index 1fa9d492ed7ce3..bdab02b4b7c80a 100644 --- a/packages/block-library/src/navigation/edit.js +++ b/packages/block-library/src/navigation/edit.js @@ -158,7 +158,11 @@ function Navigation( { isSelected ? InnerBlocks.DefaultAppender : false, - __experimentalCaptureToolbars: true, + // Ensure block toolbar is not too far removed from item + // being edited when in vertical mode. + // see: https://github.com/WordPress/gutenberg/pull/34615. + __experimentalCaptureToolbars: + attributes.orientation !== 'vertical', // Template lock set to false here so that the Nav // Block on the experimental menus screen does not // inherit templateLock={ 'all' }. From 70cbe3ca2bf21391b8c8abe0b908f53885c9b9ed Mon Sep 17 00:00:00 2001 From: Jarda Snajdr <jsnajdr@gmail.com> Date: Wed, 8 Sep 2021 14:46:28 +0200 Subject: [PATCH 207/214] Refactor the `core-data` store to thunks (#28389) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rename triggerFetch to apiFetch * Migrate __experimentalBatch * Inline apiFetch options * Remove redundant async/awaits * Remove duplicate comments (likely results of earlier merges) * Remove controls from the fully thunkified store * Adjust unit tests * Restore a few await statements before dispatch. * Restore await before dispatch( removeItems * Remove awaits from synchronous calls Co-authored-by: Adam Zieliński <adam@adamziel.com> --- package-lock.json | 1 - packages/core-data/package.json | 1 - packages/core-data/src/actions.js | 75 +++++++++++--------------- packages/core-data/src/controls.js | 31 ----------- packages/core-data/src/index.js | 3 -- packages/core-data/src/resolvers.js | 32 ++++------- packages/core-data/src/test/actions.js | 64 ++++++++++------------ 7 files changed, 67 insertions(+), 140 deletions(-) delete mode 100644 packages/core-data/src/controls.js diff --git a/package-lock.json b/package-lock.json index 8a72667def70e3..35022432dc25b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18197,7 +18197,6 @@ "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/blocks": "file:packages/blocks", "@wordpress/data": "file:packages/data", - "@wordpress/data-controls": "file:packages/data-controls", "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/element": "file:packages/element", "@wordpress/html-entities": "file:packages/html-entities", diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 557aa1c182e9fd..56fe06013a3220 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -33,7 +33,6 @@ "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/blocks": "file:../blocks", "@wordpress/data": "file:../data", - "@wordpress/data-controls": "file:../data-controls", "@wordpress/deprecated": "file:../deprecated", "@wordpress/element": "file:../element", "@wordpress/html-entities": "file:../html-entities", diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index d0d7cd12a75766..8dfbaa6571ef33 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -7,8 +7,7 @@ import { v4 as uuid } from 'uuid'; /** * WordPress dependencies */ -import { __unstableAwaitPromise } from '@wordpress/data-controls'; -import triggerFetch from '@wordpress/api-fetch'; +import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; /** @@ -17,7 +16,6 @@ import { addQueryArgs } from '@wordpress/url'; import { receiveItems, removeItems, receiveQueriedItems } from './queried-data'; import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; import { createBatch } from './batch'; -import { getDispatch } from './controls'; import { STORE_NAME } from './name'; /** @@ -168,7 +166,7 @@ export const deleteEntityRecord = ( name, recordId, query, - { __unstableFetch = triggerFetch } = {} + { __unstableFetch = apiFetch } = {} ) => async ( { dispatch } ) => { const entities = await dispatch( getKindEntities( kind ) ); const entity = find( entities, { kind, name } ); @@ -242,7 +240,7 @@ export const editEntityRecord = ( recordId, edits, options = {} -) => async ( { select, dispatch } ) => { +) => ( { select, dispatch } ) => { const entity = select.getEntity( kind, name ); if ( ! entity ) { throw new Error( @@ -270,7 +268,7 @@ export const editEntityRecord = ( }, {} ), transientEdits, }; - return await dispatch( { + dispatch( { type: 'EDIT_ENTITY_RECORD', ...edit, meta: { @@ -347,7 +345,7 @@ export const saveEntityRecord = ( kind, name, record, - { isAutosave = false, __unstableFetch = triggerFetch } = {} + { isAutosave = false, __unstableFetch = apiFetch } = {} ) => async ( { select, resolveSelect, dispatch } ) => { const entities = await dispatch( getKindEntities( kind ) ); const entity = find( entities, { kind, name } ); @@ -371,7 +369,7 @@ export const saveEntityRecord = ( const evaluatedValue = value( select.getEditedEntityRecord( kind, name, recordId ) ); - await dispatch.editEntityRecord( + dispatch.editEntityRecord( kind, name, recordId, @@ -384,7 +382,7 @@ export const saveEntityRecord = ( } } - await dispatch( { + dispatch( { type: 'SAVE_ENTITY_RECORD_START', kind, name, @@ -436,12 +434,11 @@ export const saveEntityRecord = ( : data.status, } ); - const options = { + updatedRecord = await __unstableFetch( { path: `${ path }/autosaves`, method: 'POST', data, - }; - updatedRecord = await __unstableFetch( options ); + } ); // An autosave may be processed by the server as a regular save // when its update is requested by the author and the post had @@ -477,7 +474,7 @@ export const saveEntityRecord = ( }, {} ); - await dispatch.receiveEntityRecords( + dispatch.receiveEntityRecords( kind, name, newRecord, @@ -485,7 +482,7 @@ export const saveEntityRecord = ( true ); } else { - await dispatch.receiveAutosaves( + dispatch.receiveAutosaves( persistedRecord.id, updatedRecord ); @@ -501,13 +498,12 @@ export const saveEntityRecord = ( ), }; } - const options = { + updatedRecord = await __unstableFetch( { path, method: recordId ? 'PUT' : 'POST', data: edits, - }; - updatedRecord = await __unstableFetch( options ); - await dispatch.receiveEntityRecords( + } ); + dispatch.receiveEntityRecords( kind, name, updatedRecord, @@ -530,7 +526,7 @@ export const saveEntityRecord = ( return updatedRecord; } finally { - await dispatch.__unstableReleaseStoreLock( lock ); + dispatch.__unstableReleaseStoreLock( lock ); } }; @@ -556,13 +552,12 @@ export const saveEntityRecord = ( * @return {Promise} A promise that resolves to an array containing the return * values of each function given in `requests`. */ -export function* __experimentalBatch( requests ) { +export const __experimentalBatch = ( requests ) => async ( { dispatch } ) => { const batch = createBatch(); - const dispatch = yield getDispatch(); const api = { saveEntityRecord( kind, name, record, options ) { return batch.add( ( add ) => - dispatch( STORE_NAME ).saveEntityRecord( kind, name, record, { + dispatch.saveEntityRecord( kind, name, record, { ...options, __unstableFetch: add, } ) @@ -570,38 +565,28 @@ export function* __experimentalBatch( requests ) { }, saveEditedEntityRecord( kind, name, recordId, options ) { return batch.add( ( add ) => - dispatch( STORE_NAME ).saveEditedEntityRecord( - kind, - name, - recordId, - { - ...options, - __unstableFetch: add, - } - ) + dispatch.saveEditedEntityRecord( kind, name, recordId, { + ...options, + __unstableFetch: add, + } ) ); }, deleteEntityRecord( kind, name, recordId, query, options ) { return batch.add( ( add ) => - dispatch( STORE_NAME ).deleteEntityRecord( - kind, - name, - recordId, - query, - { - ...options, - __unstableFetch: add, - } - ) + dispatch.deleteEntityRecord( kind, name, recordId, query, { + ...options, + __unstableFetch: add, + } ) ); }, }; const resultPromises = requests.map( ( request ) => request( api ) ); - const [ , ...results ] = yield __unstableAwaitPromise( - Promise.all( [ batch.run(), ...resultPromises ] ) - ); + const [ , ...results ] = await Promise.all( [ + batch.run(), + ...resultPromises, + ] ); return results; -} +}; /** * Action triggered to save an entity record's edits. diff --git a/packages/core-data/src/controls.js b/packages/core-data/src/controls.js deleted file mode 100644 index 00f8ca36641c13..00000000000000 --- a/packages/core-data/src/controls.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * WordPress dependencies - */ -import { createRegistryControl } from '@wordpress/data'; - -export function regularFetch( url ) { - return { - type: 'REGULAR_FETCH', - url, - }; -} - -export function getDispatch() { - return { - type: 'GET_DISPATCH', - }; -} - -const controls = { - async REGULAR_FETCH( { url } ) { - const { data } = await window - .fetch( url ) - .then( ( res ) => res.json() ); - - return data; - }, - - GET_DISPATCH: createRegistryControl( ( { dispatch } ) => () => dispatch ), -}; - -export default controls; diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index bcff6e6c4f4dde..7821e2ecd47459 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { createReduxStore, register } from '@wordpress/data'; -import { controls } from '@wordpress/data-controls'; /** * Internal dependencies @@ -12,7 +11,6 @@ import * as selectors from './selectors'; import * as actions from './actions'; import * as resolvers from './resolvers'; import createLocksActions from './locks/actions'; -import customControls from './controls'; import { defaultEntities, getMethodName } from './entities'; import { STORE_NAME } from './name'; @@ -58,7 +56,6 @@ const entityActions = defaultEntities.reduce( ( result, entity ) => { const storeConfig = () => ( { reducer, - controls: { ...customControls, ...controls }, actions: { ...actions, ...entityActions, ...createLocksActions() }, selectors: { ...selectors, ...entitySelectors }, resolvers: { ...resolvers, ...entityResolvers }, diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 0b02d83b064a54..49f5ae8809ab8d 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -7,16 +7,12 @@ import { find, includes, get, hasIn, compact, uniq } from 'lodash'; * WordPress dependencies */ import { addQueryArgs } from '@wordpress/url'; -import triggerFetch from '@wordpress/api-fetch'; +import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ import { STORE_NAME } from './name'; - -/** - * Internal dependencies - */ import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; import { ifNotResolved, getNormalizedCommaSeparable } from './utils'; @@ -31,7 +27,7 @@ export const getAuthors = ( query ) => async ( { dispatch } ) => { '/wp/v2/users/?who=authors&per_page=100', query ); - const users = await triggerFetch( { path } ); + const users = await apiFetch( { path } ); dispatch.receiveUserQuery( path, users ); }; @@ -39,7 +35,7 @@ export const getAuthors = ( query ) => async ( { dispatch } ) => { * Requests the current user from the REST API. */ export const getCurrentUser = () => async ( { dispatch } ) => { - const currentUser = await triggerFetch( { path: '/wp/v2/users/me' } ); + const currentUser = await apiFetch( { path: '/wp/v2/users/me' } ); dispatch.receiveCurrentUser( currentUser ); }; @@ -106,7 +102,7 @@ export const getEntityRecord = ( kind, name, key = '', query ) => async ( { } } - const record = await triggerFetch( { path } ); + const record = await apiFetch( { path } ); dispatch.receiveEntityRecords( kind, name, record, query ); } catch ( error ) { // We need a way to handle and access REST API errors in state @@ -132,14 +128,6 @@ export const getEditedEntityRecord = ifNotResolved( 'getRawEntityRecord' ); -/** - * Requests the entity's records from the REST API. - * - * @param {string} kind Entity kind. - * @param {string} name Entity name. - * @param {Object?} query Query Object. - */ - /** * Requests the entity's records from the REST API. * @@ -181,7 +169,7 @@ export const getEntityRecords = ( kind, name, query = {} ) => async ( { ...query, } ); - let records = Object.values( await triggerFetch( { path } ) ); + let records = Object.values( await apiFetch( { path } ) ); // If we request fields but the result doesn't contain the fields, // explicitely set these fields as "undefined" // that way we consider the query "fullfilled". @@ -237,7 +225,7 @@ getEntityRecords.shouldInvalidate = ( action, kind, name ) => { * Requests the current theme. */ export const getCurrentTheme = () => async ( { dispatch } ) => { - const activeThemes = await triggerFetch( { + const activeThemes = await apiFetch( { path: '/wp/v2/themes?status=active', } ); dispatch.receiveCurrentTheme( activeThemes[ 0 ] ); @@ -247,7 +235,7 @@ export const getCurrentTheme = () => async ( { dispatch } ) => { * Requests theme supports data from the index. */ export const getThemeSupports = () => async ( { dispatch } ) => { - const activeThemes = await triggerFetch( { + const activeThemes = await apiFetch( { path: '/wp/v2/themes?status=active', } ); dispatch.receiveThemeSupports( activeThemes[ 0 ].theme_supports ); @@ -260,7 +248,7 @@ export const getThemeSupports = () => async ( { dispatch } ) => { */ export const getEmbedPreview = ( url ) => async ( { dispatch } ) => { try { - const embedProxyResponse = await triggerFetch( { + const embedProxyResponse = await apiFetch( { path: addQueryArgs( '/oembed/1.0/proxy', { url } ), } ); dispatch.receiveEmbedPreview( url, embedProxyResponse ); @@ -296,7 +284,7 @@ export const canUser = ( action, resource, id ) => async ( { dispatch } ) => { let response; try { - response = await triggerFetch( { + response = await apiFetch( { path, // Ideally this would always be an OPTIONS request, but unfortunately there's // a bug in the REST API which causes the Allow header to not be sent on @@ -359,7 +347,7 @@ export const getAutosaves = ( postType, postId ) => async ( { resolveSelect, } ) => { const { rest_base: restBase } = await resolveSelect.getPostType( postType ); - const autosaves = await triggerFetch( { + const autosaves = await apiFetch( { path: `/wp/v2/${ restBase }/${ postId }/autosaves?context=edit`, } ); diff --git a/packages/core-data/src/test/actions.js b/packages/core-data/src/test/actions.js index 7e015e44442e34..8aa1ecade38548 100644 --- a/packages/core-data/src/test/actions.js +++ b/packages/core-data/src/test/actions.js @@ -33,16 +33,17 @@ describe( 'editEntityRecord', () => { const select = { getEntity: jest.fn(), }; - const fulfillment = editEntityRecord( - entity.kind, - entity.name, - entity.id, - {} - )( { select } ); - expect( select.getEntity ).toHaveBeenCalledTimes( 1 ); - await expect( fulfillment ).rejects.toThrow( + const fulfillment = () => + editEntityRecord( + entity.kind, + entity.name, + entity.id, + {} + )( { select } ); + expect( fulfillment ).toThrow( `The entity being edited (${ entity.kind }, ${ entity.name }) does not have a loaded config.` ); + expect( select.getEntity ).toHaveBeenCalledTimes( 1 ); } ); } ); @@ -386,21 +387,7 @@ describe( 'receiveCurrentUser', () => { describe( '__experimentalBatch', () => { it( 'batches multiple actions together', async () => { - const generator = __experimentalBatch( - [ - ( { saveEntityRecord: _saveEntityRecord } ) => - _saveEntityRecord( 'root', 'widget', {} ), - ( { saveEditedEntityRecord: _saveEditedEntityRecord } ) => - _saveEditedEntityRecord( 'root', 'widget', 123 ), - ( { deleteEntityRecord: _deleteEntityRecord } ) => - _deleteEntityRecord( 'root', 'widget', 123, {} ), - ], - { __unstableProcessor: ( inputs ) => Promise.resolve( inputs ) } - ); - // Run generator up to `yield getDispatch()`. - const { value: getDispatchControl } = generator.next(); - expect( getDispatchControl ).toEqual( { type: 'GET_DISPATCH' } ); - const actions = { + const dispatch = { saveEntityRecord: jest.fn( ( kind, name, record, { __unstableFetch } ) => { __unstableFetch( {} ); @@ -420,36 +407,39 @@ describe( '__experimentalBatch', () => { } ), }; - const dispatch = () => actions; - // Run generator up to `yield __unstableAwaitPromise( ... )`. - const { value: awaitPromiseControl } = generator.next( dispatch ); - expect( actions.saveEntityRecord ).toHaveBeenCalledWith( + + const results = await __experimentalBatch( + [ + ( { saveEntityRecord: _saveEntityRecord } ) => + _saveEntityRecord( 'root', 'widget', {} ), + ( { saveEditedEntityRecord: _saveEditedEntityRecord } ) => + _saveEditedEntityRecord( 'root', 'widget', 123 ), + ( { deleteEntityRecord: _deleteEntityRecord } ) => + _deleteEntityRecord( 'root', 'widget', 123, {} ), + ], + { __unstableProcessor: ( inputs ) => Promise.resolve( inputs ) } + )( { dispatch } ); + + expect( dispatch.saveEntityRecord ).toHaveBeenCalledWith( 'root', 'widget', {}, { __unstableFetch: expect.any( Function ) } ); - expect( actions.saveEditedEntityRecord ).toHaveBeenCalledWith( + expect( dispatch.saveEditedEntityRecord ).toHaveBeenCalledWith( 'root', 'widget', 123, { __unstableFetch: expect.any( Function ) } ); - expect( actions.deleteEntityRecord ).toHaveBeenCalledWith( + expect( dispatch.deleteEntityRecord ).toHaveBeenCalledWith( 'root', 'widget', 123, {}, { __unstableFetch: expect.any( Function ) } ); - expect( awaitPromiseControl ).toEqual( { - type: 'AWAIT_PROMISE', - promise: expect.any( Promise ), - } ); - // Run generator to the end. - const { value: results } = generator.next( - await awaitPromiseControl.promise - ); + expect( results ).toEqual( [ { id: 123, created: true }, { id: 123, updated: true }, From eb4c8e38f9bac57494b782f7699a5538a35dd6b8 Mon Sep 17 00:00:00 2001 From: Simon Hammes <simonhammes.27@gmail.com> Date: Wed, 8 Sep 2021 16:43:21 +0200 Subject: [PATCH 208/214] Bump jest-dev-server to v5 (#34560) Changelog: https://github.com/smooth-code/jest-puppeteer/blob/master/packages/jest-dev-server/CHANGELOG.md --- package-lock.json | 414 +++++++++++++--------------------- packages/scripts/package.json | 2 +- 2 files changed, 164 insertions(+), 252 deletions(-) diff --git a/package-lock.json b/package-lock.json index 35022432dc25b3..55adeebabc3d2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2819,53 +2819,6 @@ } } }, - "@hapi/address": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.0.0.tgz", - "integrity": "sha512-mV6T0IYqb0xL1UALPFplXYQmR0twnXG0M6jUswpquqT2sD12BOiCiLy3EvMp/Fy7s3DZElC4/aPjEjo2jeZpvw==", - "dev": true - }, - "@hapi/hoek": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-6.2.4.tgz", - "integrity": "sha512-HOJ20Kc93DkDVvjwHyHawPwPkX44sIrbXazAUDiUXaY2R9JwQGo2PhFfnQtdrsIe4igjG2fPgMra7NYw7qhy0A==", - "dev": true - }, - "@hapi/joi": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.0.tgz", - "integrity": "sha512-n6kaRQO8S+kepUTbXL9O/UOL788Odqs38/VOfoCrATDtTvyfiO3fgjlSRaNkHabpTLgM7qru9ifqXlXbXk8SeQ==", - "dev": true, - "requires": { - "@hapi/address": "2.x.x", - "@hapi/hoek": "6.x.x", - "@hapi/marker": "1.x.x", - "@hapi/topo": "3.x.x" - } - }, - "@hapi/marker": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@hapi/marker/-/marker-1.0.0.tgz", - "integrity": "sha512-JOfdekTXnJexfE8PyhZFyHvHjt81rBFSAbTIRAhF2vv/2Y1JzoKsGqxH/GpZJoF7aEfYok8JVcAHmSz1gkBieA==", - "dev": true - }, - "@hapi/topo": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.2.tgz", - "integrity": "sha512-r+aumOqJ5QbD6aLPJWqVjMAPsx5pZKz+F5yPqXZ/WWG9JTtHbQqlzrJoknJ0iJxLj9vlXtmpSdjlkszseeG8OA==", - "dev": true, - "requires": { - "@hapi/hoek": "8.x.x" - }, - "dependencies": { - "@hapi/hoek": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.0.2.tgz", - "integrity": "sha512-O6o6mrV4P65vVccxymuruucb+GhP2zl9NLCG8OdoFRS8BEGw3vwpPp20wpAtpbQQxz1CEUtmxJGgWhjq1XA3qw==", - "dev": true - } - } - }, "@istanbuljs/load-nyc-config": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz", @@ -19021,7 +18974,7 @@ "filenamify": "^4.2.0", "jest": "^26.6.3", "jest-circus": "^26.6.3", - "jest-dev-server": "^4.4.0", + "jest-dev-server": "^5.0.3", "jest-environment-node": "^26.6.2", "markdownlint": "^0.23.1", "markdownlint-cli": "^0.27.1", @@ -19045,6 +18998,153 @@ "webpack-bundle-analyzer": "^4.4.2", "webpack-cli": "^4.7.2", "webpack-livereload-plugin": "^3.0.1" + }, + "dependencies": { + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "find-process": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.4.tgz", + "integrity": "sha512-rRSuT1LE4b+BFK588D2V8/VG9liW0Ark1XJgroxZXI0LtwmQJOb490DvDYvbm+Hek9ETFzTutGfJ90gumITPhQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "commander": "^5.1.0", + "debug": "^4.1.1" + } + }, + "jest-dev-server": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-5.0.3.tgz", + "integrity": "sha512-aJR3a5KdY18Lsz+VbREKwx2HM3iukiui+J9rlv9o6iYTwZCSsJazSTStcD9K1q0AIF3oA+FqLOKDyo/sc7+fJw==", + "dev": true, + "requires": { + "chalk": "^4.1.1", + "cwd": "^0.10.0", + "find-process": "^1.4.4", + "prompts": "^2.4.1", + "spawnd": "^5.0.0", + "tree-kill": "^1.2.2", + "wait-on": "^5.3.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "prompts": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", + "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "spawnd": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-5.0.0.tgz", + "integrity": "sha512-28+AJr82moMVWolQvlAIv3JcYDkjkFTEmfDc503wxrF5l2rQ3dFz6DpbXp3kD4zmgGGldfM4xM4v1sFj/ZaIOA==", + "dev": true, + "requires": { + "exit": "^0.1.2", + "signal-exit": "^3.0.3", + "tree-kill": "^1.2.2", + "wait-port": "^0.2.9" + } + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "wait-on": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz", + "integrity": "sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg==", + "dev": true, + "requires": { + "axios": "^0.21.1", + "joi": "^17.3.0", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "rxjs": "^6.6.3" + } + }, + "wait-port": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.9.tgz", + "integrity": "sha512-hQ/cVKsNqGZ/UbZB/oakOGFqic00YAMM5/PEj3Bt4vKarv2jWIWzDbqlwT94qMs/exAQAsvMOq99sZblV92zxQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "commander": "^3.0.2", + "debug": "^4.1.1" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true + } + } + } } }, "@wordpress/server-side-render": { @@ -28143,6 +28243,15 @@ "integrity": "sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ==", "dev": true }, + "axios": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.3.tgz", + "integrity": "sha512-JtoZ3Ndke/+Iwt5n+BgSli/3idTvpt5OjKyoCmz4LX5+lPiY5l7C1colYezhlxThjNa/NhngCUWZSZFypIFuaA==", + "dev": true, + "requires": { + "follow-redirects": "^1.14.0" + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -36559,45 +36668,6 @@ "find-file-up": "^0.1.2" } }, - "find-process": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.3.tgz", - "integrity": "sha512-+IA+AUsQCf3uucawyTwMWcY+2M3FXq3BRvw3S+j5Jvydjk31f/+NPWpYZOJs+JUs2GvxH4Yfr6Wham0ZtRLlPA==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "commander": "^2.11.0", - "debug": "^2.6.8" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -36666,6 +36736,12 @@ "readable-stream": "^2.0.4" } }, + "follow-redirects": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", + "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==", + "dev": true + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -40848,78 +40924,6 @@ } } }, - "jest-dev-server": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-4.4.0.tgz", - "integrity": "sha512-STEHJ3iPSC8HbrQ3TME0ozGX2KT28lbT4XopPxUm2WimsX3fcB3YOptRh12YphQisMhfqNSNTZUmWyT3HEXS2A==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "cwd": "^0.10.0", - "find-process": "^1.4.3", - "prompts": "^2.3.0", - "spawnd": "^4.4.0", - "tree-kill": "^1.2.2", - "wait-on": "^3.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true - } - } - }, "jest-diff": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", @@ -56428,26 +56432,6 @@ "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", "dev": true }, - "spawnd": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-4.4.0.tgz", - "integrity": "sha512-jLPOfB6QOEgMOQY15Z6+lwZEhH3F5ncXxIaZ7WHPIapwNNLyjrs61okj3VJ3K6tmP5TZ6cO0VAu9rEY4MD4YQg==", - "dev": true, - "requires": { - "exit": "^0.1.2", - "signal-exit": "^3.0.2", - "tree-kill": "^1.2.2", - "wait-port": "^0.2.7" - }, - "dependencies": { - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true - } - } - }, "spdx-correct": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", @@ -60206,78 +60190,6 @@ "xml-name-validator": "^3.0.0" } }, - "wait-on": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-3.3.0.tgz", - "integrity": "sha512-97dEuUapx4+Y12aknWZn7D25kkjMk16PbWoYzpSdA8bYpVfS6hpl2a2pOWZ3c+Tyt3/i4/pglyZctG3J4V1hWQ==", - "dev": true, - "requires": { - "@hapi/joi": "^15.0.3", - "core-js": "^2.6.5", - "minimist": "^1.2.0", - "request": "^2.88.0", - "rx": "^4.1.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true - }, - "rx": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", - "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", - "dev": true - } - } - }, - "wait-port": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.7.tgz", - "integrity": "sha512-pJ6cSBIa0w1sDg4y/wXN4bmvhM9OneOvwdFHo647L2NShBi/oXG4lRaLic5cO1HaYGbUhEvratPfl/WMlIC+tg==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "commander": "^3.0.2", - "debug": "^4.1.1" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, "walker": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 78c2bc8102e6bd..a42c9a9026903e 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -59,7 +59,7 @@ "filenamify": "^4.2.0", "jest": "^26.6.3", "jest-circus": "^26.6.3", - "jest-dev-server": "^4.4.0", + "jest-dev-server": "^5.0.3", "jest-environment-node": "^26.6.2", "markdownlint": "^0.23.1", "markdownlint-cli": "^0.27.1", From cb36e1c3cf6ba3f1fd86dacbd9baea1d8f215377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 8 Sep 2021 17:40:54 +0200 Subject: [PATCH 209/214] Scripts: Add CHANGELOG entry for `jest-dev-server` upgrade (#34657) Follow-up for #34560. I have included changes to the `package-lock.json` file that hoist the dependency. --- package-lock.json | 321 ++++++++++++++++++---------------- packages/scripts/CHANGELOG.md | 8 +- 2 files changed, 180 insertions(+), 149 deletions(-) diff --git a/package-lock.json b/package-lock.json index 55adeebabc3d2c..8dfa87d9460839 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18998,153 +18998,6 @@ "webpack-bundle-analyzer": "^4.4.2", "webpack-cli": "^4.7.2", "webpack-livereload-plugin": "^3.0.1" - }, - "dependencies": { - "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "find-process": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.4.tgz", - "integrity": "sha512-rRSuT1LE4b+BFK588D2V8/VG9liW0Ark1XJgroxZXI0LtwmQJOb490DvDYvbm+Hek9ETFzTutGfJ90gumITPhQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "commander": "^5.1.0", - "debug": "^4.1.1" - } - }, - "jest-dev-server": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-5.0.3.tgz", - "integrity": "sha512-aJR3a5KdY18Lsz+VbREKwx2HM3iukiui+J9rlv9o6iYTwZCSsJazSTStcD9K1q0AIF3oA+FqLOKDyo/sc7+fJw==", - "dev": true, - "requires": { - "chalk": "^4.1.1", - "cwd": "^0.10.0", - "find-process": "^1.4.4", - "prompts": "^2.4.1", - "spawnd": "^5.0.0", - "tree-kill": "^1.2.2", - "wait-on": "^5.3.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "spawnd": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-5.0.0.tgz", - "integrity": "sha512-28+AJr82moMVWolQvlAIv3JcYDkjkFTEmfDc503wxrF5l2rQ3dFz6DpbXp3kD4zmgGGldfM4xM4v1sFj/ZaIOA==", - "dev": true, - "requires": { - "exit": "^0.1.2", - "signal-exit": "^3.0.3", - "tree-kill": "^1.2.2", - "wait-port": "^0.2.9" - } - }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "wait-on": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz", - "integrity": "sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg==", - "dev": true, - "requires": { - "axios": "^0.21.1", - "joi": "^17.3.0", - "lodash": "^4.17.21", - "minimist": "^1.2.5", - "rxjs": "^6.6.3" - } - }, - "wait-port": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.9.tgz", - "integrity": "sha512-hQ/cVKsNqGZ/UbZB/oakOGFqic00YAMM5/PEj3Bt4vKarv2jWIWzDbqlwT94qMs/exAQAsvMOq99sZblV92zxQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "commander": "^3.0.2", - "debug": "^4.1.1" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", - "dev": true - } - } - } } }, "@wordpress/server-side-render": { @@ -36668,6 +36521,40 @@ "find-file-up": "^0.1.2" } }, + "find-process": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.4.tgz", + "integrity": "sha512-rRSuT1LE4b+BFK588D2V8/VG9liW0Ark1XJgroxZXI0LtwmQJOb490DvDYvbm+Hek9ETFzTutGfJ90gumITPhQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "commander": "^5.1.0", + "debug": "^4.1.1" + }, + "dependencies": { + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -40924,6 +40811,45 @@ } } }, + "jest-dev-server": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-5.0.3.tgz", + "integrity": "sha512-aJR3a5KdY18Lsz+VbREKwx2HM3iukiui+J9rlv9o6iYTwZCSsJazSTStcD9K1q0AIF3oA+FqLOKDyo/sc7+fJw==", + "dev": true, + "requires": { + "chalk": "^4.1.1", + "cwd": "^0.10.0", + "find-process": "^1.4.4", + "prompts": "^2.4.1", + "spawnd": "^5.0.0", + "tree-kill": "^1.2.2", + "wait-on": "^5.3.0" + }, + "dependencies": { + "prompts": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", + "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + } + } + }, "jest-diff": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", @@ -56432,6 +56358,32 @@ "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", "dev": true }, + "spawnd": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-5.0.0.tgz", + "integrity": "sha512-28+AJr82moMVWolQvlAIv3JcYDkjkFTEmfDc503wxrF5l2rQ3dFz6DpbXp3kD4zmgGGldfM4xM4v1sFj/ZaIOA==", + "dev": true, + "requires": { + "exit": "^0.1.2", + "signal-exit": "^3.0.3", + "tree-kill": "^1.2.2", + "wait-port": "^0.2.9" + }, + "dependencies": { + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + } + } + }, "spdx-correct": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", @@ -60190,6 +60142,81 @@ "xml-name-validator": "^3.0.0" } }, + "wait-on": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz", + "integrity": "sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg==", + "dev": true, + "requires": { + "axios": "^0.21.1", + "joi": "^17.3.0", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "rxjs": "^6.6.3" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "wait-port": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.9.tgz", + "integrity": "sha512-hQ/cVKsNqGZ/UbZB/oakOGFqic00YAMM5/PEj3Bt4vKarv2jWIWzDbqlwT94qMs/exAQAsvMOq99sZblV92zxQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "commander": "^3.0.2", + "debug": "^4.1.1" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "walker": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 3ff2f06c87ae31..37d3a111e51ab3 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancements + +- The bundled `jest-dev-server` dependency has been updated to the next major version `^5.0.3` ([#34560](https://github.com/WordPress/gutenberg/pull/34560)). + ### Bug Fixes - Bring back support for SVG files in CSS ([#34394](https://github.com/WordPress/gutenberg/pull/34394)). It wasn't correctly migrated when integrating webpack v5. @@ -11,8 +15,8 @@ ### Breaking Changes - Increase the minimum Node.js version to v12.13 matching requirements from bundled dependencies ([#33818](https://github.com/WordPress/gutenberg/pull/33818)). -- The bundled `webpack` dependency has been updated to the next major version `^5.47.1` (see [Breaking Changes](https://webpack.js.org/migrate/5/), [##33818](https://github.com/WordPress/gutenberg/pull/#33818)). -- The bundled `webpack-cli` dependency has been updated to the next major version `^4.7.2` ([##33818](https://github.com/WordPress/gutenberg/pull/#33818)). +- The bundled `webpack` dependency has been updated to the next major version `^5.47.1` (see [Breaking Changes](https://webpack.js.org/migrate/5/), [#33818](https://github.com/WordPress/gutenberg/pull/33818)). +- The bundled `webpack-cli` dependency has been updated to the next major version `^4.7.2` ([#33818](https://github.com/WordPress/gutenberg/pull/33818)). - The bundled `css-loader` dependency has been updated from requiring `^5.1.3` to requiring `^6.2.0` ([#33818](https://github.com/WordPress/gutenberg/pull/33818)). - The bundled `file-loader` dependency has been removed ([#33818](https://github.com/WordPress/gutenberg/pull/33818)). - The bundled `ignore-emit-webpack-plugin` dependency has been removed ([#33818](https://github.com/WordPress/gutenberg/pull/33818)). From b2208814a49c12ad206396502fb72ac91af52f4d Mon Sep 17 00:00:00 2001 From: Renatho De Carli Rosa <renatho@gmail.com> Date: Wed, 8 Sep 2021 13:03:35 -0300 Subject: [PATCH 210/214] ESLint Plugin: Update eslint jsdoc dependency (#34338) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update eslint-plugin-jsdoc dependency * Fix JSDoc comment * Update eslint rules disabling some of them It disables some rules that are failing after the eslint plugin update. * Update no-multi-asterisks rule options * Enable and fix jsdoc/valid-types eslint rule * Update CHANGELOG.md * Fix jsdoc line alignment Co-authored-by: Renatho De Carli Rosa <renatho@automattic.com> Co-authored-by: Greg Ziółkowski <grzegorz@gziolo.pl> --- package-lock.json | 66 ++++++++++--------- packages/block-editor/src/store/actions.js | 10 +++ packages/block-editor/src/store/selectors.js | 2 + .../src/gallery/v1/tiles.native.js | 3 +- packages/components/src/animate/index.js | 2 +- .../src/ui/context/use-context-system.js | 2 +- .../dom/src/dom/caret-range-from-point.js | 4 +- packages/eslint-plugin/CHANGELOG.md | 4 ++ packages/eslint-plugin/configs/jsdoc.js | 5 ++ packages/eslint-plugin/package.json | 2 +- 10 files changed, 64 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8dfa87d9460839..98cb8029eb2116 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2286,16 +2286,22 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@es-joy/jsdoccomment": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.4.4.tgz", - "integrity": "sha512-ua4qDt9dQb4qt5OI38eCZcQZYE5Bq3P0GzgvDARdT8Lt0mAUpxKTPy8JGGqEvF77tG1irKDZ3WreeezEa3P43w==", + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.10.8.tgz", + "integrity": "sha512-3P1JiGL4xaR9PoTKUHa2N/LKwa2/eUdRqGwijMWWgBqbFEqJUVpmaOi2TcjcemrsRMgFLBzQCK4ToPhrSVDiFQ==", "dev": true, "requires": { - "comment-parser": "^1.1.5", + "comment-parser": "1.2.4", "esquery": "^1.4.0", - "jsdoctypeparser": "^9.0.0" + "jsdoc-type-pratt-parser": "1.1.1" }, "dependencies": { + "comment-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.2.4.tgz", + "integrity": "sha512-pm0b+qv+CkWNriSTMsfnjChF9kH0kxz55y44Wo5le9qLxMj5xDQAaEd9ZN1ovSuk9CsrncWaFwgpOMg7ClJwkw==", + "dev": true + }, "esquery": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", @@ -18572,7 +18578,7 @@ "eslint-config-prettier": "^7.1.0", "eslint-plugin-import": "^2.23.4", "eslint-plugin-jest": "^24.1.3", - "eslint-plugin-jsdoc": "^34.1.0", + "eslint-plugin-jsdoc": "^36.0.8", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-prettier": "^3.3.0", "eslint-plugin-react": "^7.22.0", @@ -34672,26 +34678,32 @@ } }, "eslint-plugin-jsdoc": { - "version": "34.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-34.1.0.tgz", - "integrity": "sha512-7uk6vD92LCGBLwl7imvf7YzZrMbLmHZVSULBJClZpYTNdTpPXOtuPNKDi8nLcXYtZf3UopNs5qR7coapBSaUtw==", + "version": "36.0.8", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-36.0.8.tgz", + "integrity": "sha512-brNjHvRuBy5CaV01mSp6WljrO/T8fHNj0DXG38odOGDnhI7HdcbLKX7DpSvg2Rfcifwh8GlnNFzx13sI05t3bg==", "dev": true, "requires": { - "@es-joy/jsdoccomment": "^0.4.4", - "comment-parser": "1.1.5", - "debug": "^4.3.1", + "@es-joy/jsdoccomment": "0.10.8", + "comment-parser": "1.2.4", + "debug": "^4.3.2", "esquery": "^1.4.0", - "jsdoctypeparser": "^9.0.0", + "jsdoc-type-pratt-parser": "^1.1.1", "lodash": "^4.17.21", - "regextras": "^0.7.1", + "regextras": "^0.8.0", "semver": "^7.3.5", "spdx-expression-parse": "^3.0.1" }, "dependencies": { + "comment-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.2.4.tgz", + "integrity": "sha512-pm0b+qv+CkWNriSTMsfnjChF9kH0kxz55y44Wo5le9qLxMj5xDQAaEd9ZN1ovSuk9CsrncWaFwgpOMg7ClJwkw==", + "dev": true + }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { "ms": "2.1.2" @@ -34712,12 +34724,6 @@ "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -44203,10 +44209,10 @@ } } }, - "jsdoctypeparser": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz", - "integrity": "sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==", + "jsdoc-type-pratt-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-1.1.1.tgz", + "integrity": "sha512-uelRmpghNwPBuZScwgBG/OzodaFk5RbO5xaivBdsAY70icWfShwZ7PCMO0x1zSkOa8T1FzHThmrdoyg/0AwV5g==", "dev": true }, "jsdom": { @@ -54226,9 +54232,9 @@ } }, "regextras": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz", - "integrity": "sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.8.0.tgz", + "integrity": "sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ==", "dev": true }, "regjsgen": { diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 38dd3500e7d871..0e6a3f50974110 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -123,6 +123,7 @@ export function* validateBlocksToTemplate( blocks ) { * text value. See `wp.richText.create`. */ +/* eslint-disable jsdoc/valid-types */ /** * Returns an action object used in signalling that selection state should be * reset to the specified selection. @@ -138,6 +139,7 @@ export function resetSelection( selectionEnd, initialPosition ) { + /* eslint-enable jsdoc/valid-types */ return { type: 'RESET_SELECTION', selectionStart, @@ -209,6 +211,7 @@ export function updateBlock( clientId, updates ) { }; } +/* eslint-disable jsdoc/valid-types */ /** * Returns an action object used in signalling that the block with the * specified client ID has been selected, optionally accepting a position @@ -222,6 +225,7 @@ export function updateBlock( clientId, updates ) { * @return {Object} Action object. */ export function selectBlock( clientId, initialPosition = 0 ) { + /* eslint-enable jsdoc/valid-types */ return { type: 'SELECT_BLOCK', initialPosition, @@ -389,6 +393,7 @@ function getBlocksWithDefaultStylesApplied( blocks, blockEditorSettings ) { } ); } +/* eslint-disable jsdoc/valid-types */ /** * Returns an action object signalling that a blocks should be replaced with * one or more replacement blocks. @@ -408,6 +413,7 @@ export function* replaceBlocks( initialPosition = 0, meta ) { + /* eslint-enable jsdoc/valid-types */ clientIds = castArray( clientIds ); blocks = getBlocksWithDefaultStylesApplied( castArray( blocks ), @@ -594,6 +600,7 @@ export function insertBlock( ); } +/* eslint-disable jsdoc/valid-types */ /** * Returns an action object used in signalling that an array of blocks should * be inserted, optionally at a specific index respective a root block list. @@ -614,6 +621,7 @@ export function* insertBlocks( initialPosition = 0, meta ) { + /* eslint-enable jsdoc/valid-types */ if ( isObject( initialPosition ) ) { meta = initialPosition; initialPosition = 0; @@ -954,6 +962,7 @@ export function removeBlock( clientId, selectPrevious ) { return removeBlocks( [ clientId ], selectPrevious ); } +/* eslint-disable jsdoc/valid-types */ /** * Returns an action object used in signalling that the inner blocks with the * specified client ID should be replaced. @@ -970,6 +979,7 @@ export function replaceInnerBlocks( updateSelection = false, initialPosition = 0 ) { + /* eslint-enable jsdoc/valid-types */ return { type: 'REPLACE_INNER_BLOCKS', rootClientId, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index d10a80d83e2451..6a51a62209d983 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -636,6 +636,7 @@ export function getNextBlockClientId( state, startClientId ) { return getAdjacentBlockClientId( state, startClientId, 1 ); } +/* eslint-disable jsdoc/valid-types */ /** * Returns the initial caret position for the selected block. * This position is to used to position the caret properly when the selected block changes. @@ -646,6 +647,7 @@ export function getNextBlockClientId( state, startClientId ) { * @return {0|-1|null} Initial position. */ export function getSelectedBlocksInitialCaretPosition( state ) { + /* eslint-enable jsdoc/valid-types */ return state.initialPosition; } diff --git a/packages/block-library/src/gallery/v1/tiles.native.js b/packages/block-library/src/gallery/v1/tiles.native.js index c5ce52b4ba24ba..30126ce872593e 100644 --- a/packages/block-library/src/gallery/v1/tiles.native.js +++ b/packages/block-library/src/gallery/v1/tiles.native.js @@ -23,7 +23,8 @@ function Tiles( props ) { const lastRow = Math.floor( lastTile / columns ); const wrappedChildren = Children.map( children, ( child, index ) => { - /** Since we don't have `calc()`, we must calculate our spacings here in + /** + * Since we don't have `calc()`, we must calculate our spacings here in * order to preserve even spacing between tiles and equal width for tiles * in a given row. * diff --git a/packages/components/src/animate/index.js b/packages/components/src/animate/index.js index 95fba645fd1782..1ef1e272dbb453 100644 --- a/packages/components/src/animate/index.js +++ b/packages/components/src/animate/index.js @@ -8,7 +8,7 @@ import classnames from 'classnames'; * @typedef {'left' | 'right'} SlideInOrigin * @typedef {{ type: 'appear'; origin?: AppearOrigin }} AppearOptions * @typedef {{ type: 'slide-in'; origin?: SlideInOrigin }} SlideInOptions - * @typedef {{ type: 'loading'; }} LoadingOptions + * @typedef {{ type: 'loading' }} LoadingOptions * @typedef {AppearOptions | SlideInOptions | LoadingOptions} GetAnimateOptions */ diff --git a/packages/components/src/ui/context/use-context-system.js b/packages/components/src/ui/context/use-context-system.js index 4a842033a2a2f3..134ef9046b849d 100644 --- a/packages/components/src/ui/context/use-context-system.js +++ b/packages/components/src/ui/context/use-context-system.js @@ -13,7 +13,7 @@ import { useCx } from '../../utils/hooks/use-cx'; /** * @template TProps - * @typedef {TProps & { className: string; }} ConnectedProps + * @typedef {TProps & { className: string }} ConnectedProps */ /** diff --git a/packages/dom/src/dom/caret-range-from-point.js b/packages/dom/src/dom/caret-range-from-point.js index a3f83926f083a2..8839e0861f0ab3 100644 --- a/packages/dom/src/dom/caret-range-from-point.js +++ b/packages/dom/src/dom/caret-range-from-point.js @@ -5,8 +5,8 @@ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/caretRangeFromPoint * * @param {DocumentMaybeWithCaretPositionFromPoint} doc The document of the range. - * @param {number} x Horizontal position within the current viewport. - * @param {number} y Vertical position within the current viewport. + * @param {number} x Horizontal position within the current viewport. + * @param {number} y Vertical position within the current viewport. * * @return {Range | null} The best range for the given point. */ diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 5b8830ff10c976..f1df1f4c7433e3 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancement + +- The bundled `eslint-plugin-jsdoc` dependency has been updated from requiring `^34.1.0` to requiring `^36.0.8` ([#34338](https://github.com/WordPress/gutenberg/pull/34338)). + ### Bug Fix - The recommended configuration will now respect `type` imports in TypeScript files ([#34055](https://github.com/WordPress/gutenberg/pull/34055)). diff --git a/packages/eslint-plugin/configs/jsdoc.js b/packages/eslint-plugin/configs/jsdoc.js index b8f658433aad22..d114241361a070 100644 --- a/packages/eslint-plugin/configs/jsdoc.js +++ b/packages/eslint-plugin/configs/jsdoc.js @@ -105,6 +105,11 @@ module.exports = { 'jsdoc/require-param-description': 'off', 'jsdoc/require-returns': 'off', 'jsdoc/require-yields': 'off', + 'jsdoc/tag-lines': 'off', + 'jsdoc/no-multi-asterisks': [ + 'error', + { preventAtMiddleLines: false }, + ], 'jsdoc/check-access': 'error', 'jsdoc/check-alignment': 'error', 'jsdoc/check-line-alignment': [ diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index ed0b6d76cea633..29a7afbc65f507 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -39,7 +39,7 @@ "eslint-config-prettier": "^7.1.0", "eslint-plugin-import": "^2.23.4", "eslint-plugin-jest": "^24.1.3", - "eslint-plugin-jsdoc": "^34.1.0", + "eslint-plugin-jsdoc": "^36.0.8", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-prettier": "^3.3.0", "eslint-plugin-react": "^7.22.0", From 82e578dcc75e67891c750a41a04c1e31994192fc Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco <gerardo.pacheco@automattic.com> Date: Wed, 8 Sep 2021 18:32:43 +0200 Subject: [PATCH 211/214] [Mobile] - Global styles - Add support to render font sizes and line height (#34144) * Mobile - Global styles - Add support to render font sizes * Mobile - Rich text - Trigger font size update in the native side of aztec * Mobile - Temp - Comment out line height removal * Fix Android lineHeight control changes. * Mobile - Global styles - Get font sizes values * Mobile - AztecView - Support line height * Mobile - Update AztecView comment for line height values * Mobile - Line height support - iOS tweaks and makes it available only behind DEV * Mobile - Update snapshots * Mobile - Remove default line height * Mobile - Rich text - Unify force native update for font size and line height updates Co-authored-by: Enej Bajgoric <enej.bajgoric@automattic.com> --- .../src/components/block-list/block.native.js | 4 +- .../test/__snapshots__/edit.native.js.snap | 4 + .../test/__snapshots__/edit.native.js.snap | 7 ++ .../global-styles-context/index.native.js | 17 ++++- .../global-styles-context/utils.native.js | 76 ++++++++++++++++++- .../ReactNativeAztec/ReactAztecManager.java | 6 ++ .../ios/RNTAztecView/RCTAztecView.swift | 31 +++++++- .../ios/RNTAztecView/RCTAztecViewManager.m | 1 + packages/react-native-aztec/src/AztecView.js | 6 +- .../rich-text/src/component/index.native.js | 73 +++++++++++++----- 10 files changed, 195 insertions(+), 30 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index b7543dc7977671..699145b6ffc528 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -51,6 +51,7 @@ function BlockForType( { baseGlobalStyles, } ) { const defaultColors = useSetting( 'color.palette' ) || emptyArray; + const fontSizes = useSetting( 'typography.fontSizes' ) || emptyArray; const globalStyle = useGlobalStyles(); const mergedStyle = useMemo( () => { return getMergedGlobalStyles( @@ -59,7 +60,8 @@ function BlockForType( { wrapperProps.style, attributes, defaultColors, - name + name, + fontSizes ); }, [ defaultColors, diff --git a/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap index 36a2e8b87915d4..440d991192d164 100644 --- a/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap @@ -61,6 +61,7 @@ exports[`File block renders file error state without crashing 1`] = ` disableEditingMenu={false} focusable={true} fontFamily="serif" + fontSize={16} isMultiline={false} maxImagesWidth={200} onBackspace={[Function]} @@ -150,6 +151,7 @@ exports[`File block renders file error state without crashing 1`] = ` disableEditingMenu={false} focusable={true} fontFamily="serif" + fontSize={16} isMultiline={false} maxImagesWidth={200} minWidth={40} @@ -259,6 +261,7 @@ exports[`File block renders file without crashing 1`] = ` disableEditingMenu={false} focusable={true} fontFamily="serif" + fontSize={16} isMultiline={false} maxImagesWidth={200} onBackspace={[Function]} @@ -330,6 +333,7 @@ exports[`File block renders file without crashing 1`] = ` disableEditingMenu={false} focusable={true} fontFamily="serif" + fontSize={16} isMultiline={false} maxImagesWidth={200} minWidth={40} diff --git a/packages/block-library/src/search/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/search/test/__snapshots__/edit.native.js.snap index 25ce13f150a1df..6030decd6fcf51 100644 --- a/packages/block-library/src/search/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/search/test/__snapshots__/edit.native.js.snap @@ -23,6 +23,7 @@ exports[`Search Block renders block with button inside option 1`] = ` disableEditingMenu={false} focusable={true} fontFamily="serif" + fontSize={16} isMultiline={false} maxImagesWidth={200} onBackspace={[Function]} @@ -126,6 +127,7 @@ exports[`Search Block renders block with button inside option 1`] = ` disableEditingMenu={false} focusable={true} fontFamily="serif" + fontSize={16} isMultiline={false} maxImagesWidth={200} minWidth={75} @@ -196,6 +198,7 @@ exports[`Search Block renders block with icon button option matches snapshot 1`] disableEditingMenu={false} focusable={true} fontFamily="serif" + fontSize={16} isMultiline={false} maxImagesWidth={200} onBackspace={[Function]} @@ -356,6 +359,7 @@ exports[`Search Block renders block with label hidden matches snapshot 1`] = ` disableEditingMenu={false} focusable={true} fontFamily="serif" + fontSize={16} isMultiline={false} maxImagesWidth={200} minWidth={75} @@ -426,6 +430,7 @@ exports[`Search Block renders with default configuration matches snapshot 1`] = disableEditingMenu={false} focusable={true} fontFamily="serif" + fontSize={16} isMultiline={false} maxImagesWidth={200} onBackspace={[Function]} @@ -529,6 +534,7 @@ exports[`Search Block renders with default configuration matches snapshot 1`] = disableEditingMenu={false} focusable={true} fontFamily="serif" + fontSize={16} isMultiline={false} maxImagesWidth={200} minWidth={75} @@ -599,6 +605,7 @@ exports[`Search Block renders with no-button option matches snapshot 1`] = ` disableEditingMenu={false} focusable={true} fontFamily="serif" + fontSize={16} isMultiline={false} maxImagesWidth={200} onBackspace={[Function]} diff --git a/packages/components/src/mobile/global-styles-context/index.native.js b/packages/components/src/mobile/global-styles-context/index.native.js index e33e86d636aafe..a0fd4ff32691d1 100644 --- a/packages/components/src/mobile/global-styles-context/index.native.js +++ b/packages/components/src/mobile/global-styles-context/index.native.js @@ -15,6 +15,7 @@ import { BLOCK_STYLE_ATTRIBUTES, getBlockPaddings, getBlockColors, + getBlockTypography, } from './utils'; const GlobalStylesContext = createContext( { style: {} } ); @@ -27,7 +28,8 @@ export const getMergedGlobalStyles = ( wrapperPropsStyle, blockAttributes, defaultColors, - blockName + blockName, + fontSizes ) => { const baseGlobalColors = { baseColors: baseGlobalStyles || {}, @@ -60,8 +62,19 @@ export const getMergedGlobalStyles = ( blockStyleAttributes, blockColors ); + const blockTypography = getBlockTypography( + blockStyleAttributes, + fontSizes, + blockName, + baseGlobalStyles + ); - return { ...mergedStyle, ...blockPaddings, ...blockColors }; + return { + ...mergedStyle, + ...blockPaddings, + ...blockColors, + ...blockTypography, + }; }; export const useGlobalStyles = () => { diff --git a/packages/components/src/mobile/global-styles-context/utils.native.js b/packages/components/src/mobile/global-styles-context/utils.native.js index 97aee36a6f36b1..ed65c255c8f264 100644 --- a/packages/components/src/mobile/global-styles-context/utils.native.js +++ b/packages/components/src/mobile/global-styles-context/utils.native.js @@ -1,13 +1,14 @@ /** * External dependencies */ -import { find, startsWith, get } from 'lodash'; +import { find, startsWith, get, camelCase, has } from 'lodash'; export const BLOCK_STYLE_ATTRIBUTES = [ 'textColor', 'backgroundColor', 'style', 'color', + 'fontSize', ]; // Mapping style properties name to native @@ -121,6 +122,62 @@ export function getBlockColors( return blockStyles; } +export function getBlockTypography( + blockStyleAttributes, + fontSizes, + blockName, + baseGlobalStyles +) { + const typographyStyles = {}; + const customBlockStyles = blockStyleAttributes?.style?.typography || {}; + const blockGlobalStyles = baseGlobalStyles?.blocks?.[ blockName ]; + + // Global styles + if ( blockGlobalStyles?.typography ) { + const fontSize = blockGlobalStyles?.typography?.fontSize; + const lineHeight = blockGlobalStyles?.typography?.lineHeight; + + if ( fontSize ) { + if ( parseInt( fontSize, 10 ) ) { + typographyStyles.fontSize = fontSize; + } else { + const mappedFontSize = find( fontSizes, { + slug: fontSize, + } ); + + if ( mappedFontSize ) { + typographyStyles.fontSize = mappedFontSize?.size; + } + } + } + + if ( lineHeight ) { + typographyStyles.lineHeight = lineHeight; + } + } + + if ( blockStyleAttributes?.fontSize ) { + const mappedFontSize = find( fontSizes, { + slug: blockStyleAttributes?.fontSize, + } ); + + if ( mappedFontSize ) { + typographyStyles.fontSize = mappedFontSize?.size; + } + } + + // Custom styles + if ( customBlockStyles?.fontSize ) { + typographyStyles.fontSize = customBlockStyles?.fontSize; + } + + if ( customBlockStyles?.lineHeight ) { + typographyStyles.lineHeight = customBlockStyles?.lineHeight; + } + + return typographyStyles; +} + export function parseStylesVariables( styles, mappedValues, customValues ) { let stylesBase = styles; const variables = [ 'preset', 'custom' ]; @@ -152,7 +209,15 @@ export function parseStylesVariables( styles, mappedValues, customValues ) { const customValuesData = customValues ?? JSON.parse( stylesBase ); stylesBase = stylesBase.replace( regex, ( _$1, $2 ) => { const path = $2.split( '--' ); - return get( customValuesData, path ); + if ( has( customValuesData, path ) ) { + return get( customValuesData, path ); + } + + // Check for camelcase properties + return get( customValuesData, [ + ...path.slice( 0, path.length - 1 ), + camelCase( path[ path.length - 1 ] ), + ] ); } ); } } ); @@ -161,14 +226,19 @@ export function parseStylesVariables( styles, mappedValues, customValues ) { } export function getMappedValues( features, palette ) { + const typography = features?.typography; const colors = { ...palette?.theme, ...palette?.user }; + const fontSizes = { + ...typography?.fontSizes?.theme, + ...typography?.fontSizes?.user, + }; const mappedValues = { color: { values: colors, slug: 'color', }, 'font-size': { - values: features?.typography?.fontSizes?.theme, + values: fontSizes, slug: 'size', }, }; diff --git a/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java b/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java index b1652093b89807..62f640f0c9f841 100644 --- a/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java +++ b/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java @@ -288,6 +288,12 @@ public void setFontSize(ReactAztecText view, float fontSize) { (int) Math.ceil(PixelUtil.toPixelFromSP(fontSize))); } + @ReactProp(name = ViewProps.LINE_HEIGHT) + public void setLineHeight(ReactAztecText view, float lineHeight) { + float textSize = view.getTextSize(); + view.setLineSpacing(textSize * lineHeight, (float) (lineHeight / textSize)); + } + @ReactProp(name = ViewProps.FONT_FAMILY) public void setFontFamily(ReactAztecText view, String fontFamily) { int style = Typeface.NORMAL; diff --git a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift index c87316600177ee..cdd28ea38d39eb 100644 --- a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift +++ b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift @@ -41,7 +41,7 @@ class RCTAztecView: Aztec.TextView { get { return super.textAlignment } - } + } private var previousContentSize: CGSize = .zero @@ -109,6 +109,10 @@ class RCTAztecView: Aztec.TextView { /// Font weight for all contents. Once this is set, it will always override the font weight for all of its /// contents, regardless of what HTML is provided to Aztec. private var fontWeight: String? = nil + + /// Line height for all contents. Once this is set, it will always override the font size for all of its + /// contents, regardless of what HTML is provided to Aztec. + private var lineHeight: CGFloat? = nil // MARK: - Formats @@ -588,6 +592,7 @@ class RCTAztecView: Aztec.TextView { } fontSize = size refreshFont() + refreshLineHeight() } @objc func setFontWeight(_ weight: String) { @@ -597,6 +602,14 @@ class RCTAztecView: Aztec.TextView { fontWeight = weight refreshFont() } + + @objc func setLineHeight(_ newLineHeight: CGFloat) { + guard lineHeight != newLineHeight else { + return + } + lineHeight = newLineHeight + refreshLineHeight() + } // MARK: - Font Refreshing @@ -650,9 +663,23 @@ class RCTAztecView: Aztec.TextView { /// This method should not be called directly. Call `refreshFont()` instead. /// private func refreshTypingAttributesAndPlaceholderFont() { - let currentFont = font(from: typingAttributes) + let currentFont = font(from: typingAttributes) placeholderLabel.font = currentFont } + + /// This method refreshes the line height. + private func refreshLineHeight() { + if let lineHeight = lineHeight { + let attributeString = NSMutableAttributedString(string: self.text) + let style = NSMutableParagraphStyle() + let currentFontSize = fontSize ?? defaultFont.pointSize + let lineSpacing = ((currentFontSize * lineHeight) / UIScreen.main.scale) - (currentFontSize / lineHeight) / 2 + + style.lineSpacing = lineSpacing + defaultParagraphStyle.regularLineSpacing = lineSpacing + textStorage.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: NSMakeRange(0, textStorage.length)) + } + } // MARK: - Formatting interface diff --git a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m index db468a71be0b8f..5f88fd15e83e1c 100644 --- a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m +++ b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m @@ -27,6 +27,7 @@ @interface RCT_EXTERN_MODULE(RCTAztecViewManager, NSObject) RCT_EXPORT_VIEW_PROPERTY(fontFamily, NSString) RCT_EXPORT_VIEW_PROPERTY(fontSize, CGFloat) RCT_EXPORT_VIEW_PROPERTY(fontWeight, NSString) +RCT_EXPORT_VIEW_PROPERTY(lineHeight, CGFloat) RCT_EXPORT_VIEW_PROPERTY(disableEditingMenu, BOOL) RCT_REMAP_VIEW_PROPERTY(textAlign, textAlignment, NSTextAlignment) diff --git a/packages/react-native-aztec/src/AztecView.js b/packages/react-native-aztec/src/AztecView.js index f08bb172dd15fb..9c5e09c1313669 100644 --- a/packages/react-native-aztec/src/AztecView.js +++ b/packages/react-native-aztec/src/AztecView.js @@ -220,10 +220,8 @@ class AztecView extends Component { window.console.warn( "Removing lineHeight style as it's not supported by native AztecView" ); - // IMPORTANT: Current Gutenberg implementation is supporting line-height without unit e.g. 'line-height':1.5 - // and library which we are using to convert css to react-native requires unit to be included with dimension - // https://github.com/kristerkari/css-to-react-native-transform/blob/945866e84a505fdfb1a43b03ebe4bd32784a7f22/src/index.spec.js#L1234 - // which means that we would need to patch the library if we want to support line-height from native AztecView in the future. + // Prevents passing line-heigth within styles to avoid a crash due to values without units + // We now support this but passing line-height as a prop instead } return ( diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 194fadd82cf0d0..ec798d3c915469 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -4,7 +4,7 @@ * External dependencies */ import { View, Platform } from 'react-native'; -import { get, pickBy, debounce, isString } from 'lodash'; +import { get, pickBy, debounce } from 'lodash'; import memize from 'memize'; /** @@ -56,6 +56,7 @@ const gutenbergFormatNamesToAztec = { }; const EMPTY_PARAGRAPH_TAGS = '<p></p>'; +const DEFAULT_FONT_SIZE = 16; export class RichText extends Component { constructor( { @@ -111,9 +112,6 @@ export class RichText extends Component { ).bind( this ); this.suggestionOptions = this.suggestionOptions.bind( this ); this.insertString = this.insertString.bind( this ); - this.convertFontSizeFromString = this.convertFontSizeFromString.bind( - this - ); this.manipulateEventCounterToForceNativeToRefresh = this.manipulateEventCounterToForceNativeToRefresh.bind( this ); @@ -277,13 +275,6 @@ export class RichText extends Component { .replace( closingTagRegexp, '' ); } - // Fix for crash https://github.com/wordpress-mobile/gutenberg-mobile/issues/2991 - convertFontSizeFromString( fontSize ) { - return fontSize && isString( fontSize ) && fontSize.endsWith( 'px' ) - ? parseFloat( fontSize.substring( 0, fontSize.length - 2 ) ) - : fontSize; - } - /* * Handles any case where the content of the AztecRN instance has changed */ @@ -764,6 +755,14 @@ export class RichText extends Component { this.needsSelectionUpdate = true; this.manipulateEventCounterToForceNativeToRefresh(); // force a refresh on the native side } + + if ( + nextProps?.style?.fontSize !== this.props?.style?.fontSize || + nextProps?.style?.lineHeight !== this.props?.style?.lineHeight + ) { + this.needsSelectionUpdate = true; + this.manipulateEventCounterToForceNativeToRefresh(); // force a refresh on the native side + } } return true; @@ -853,6 +852,44 @@ export class RichText extends Component { }; } + getFontSize() { + const { baseGlobalStyles } = this.props; + + if ( this.props.fontSize ) { + return parseFloat( this.props.fontSize ); + } + + if ( this.props.style?.fontSize ) { + return parseFloat( this.props.style.fontSize ); + } + + if ( baseGlobalStyles?.typography?.fontSize ) { + return parseFloat( baseGlobalStyles?.typography?.fontSize ); + } + + return DEFAULT_FONT_SIZE; + } + + getLineHeight() { + const { baseGlobalStyles } = this.props; + let lineHeight; + + // eslint-disable-next-line no-undef + if ( ! __DEV__ ) { + return; + } + + if ( baseGlobalStyles?.typography?.lineHeight ) { + lineHeight = parseFloat( baseGlobalStyles?.typography?.lineHeight ); + } + + if ( this.props.style?.lineHeight ) { + lineHeight = parseFloat( this.props.style.lineHeight ); + } + + return lineHeight; + } + render() { const { tagName, @@ -879,6 +916,8 @@ export class RichText extends Component { ); const { color: defaultPlaceholderTextColor } = placeholderStyle; + const fontSize = this.getFontSize(); + const lineHeight = this.getLineHeight(); const { color: defaultColor, @@ -1017,11 +1056,8 @@ export class RichText extends Component { } maxImagesWidth={ 200 } fontFamily={ this.props.fontFamily || defaultFontFamily } - fontSize={ - this.props.fontSize || - ( style && - this.convertFontSizeFromString( style.fontSize ) ) - } + fontSize={ fontSize } + lineHeight={ lineHeight } fontWeight={ this.props.fontWeight } fontStyle={ this.props.fontStyle } disableEditingMenu={ disableEditingMenu } @@ -1079,8 +1115,9 @@ export default compose( [ 'attributes', 'childrenStyles', ] ); - const baseGlobalStyles = getSettings() - ?.__experimentalGlobalStylesBaseStyles; + + const settings = getSettings(); + const baseGlobalStyles = settings?.__experimentalGlobalStylesBaseStyles; return { areMentionsSupported: From f3957112c50c50d7bb52ebf550294912e230e66d Mon Sep 17 00:00:00 2001 From: Dave Smith <getdavemail@gmail.com> Date: Wed, 8 Sep 2021 18:22:46 +0100 Subject: [PATCH 212/214] Fix disabled blocks logical error on Widgets screen (#34634) * Ensure result is always iterable * Invert ternary to please the JS gods Co-authored-by: Kai Hao <kevin830726@gmail.com> Co-authored-by: Kai Hao <kevin830726@gmail.com> --- packages/edit-widgets/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index c45bb294ab46fa..0c00388e7c0f2f 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -37,7 +37,7 @@ import { const disabledBlocks = [ 'core/more', 'core/freeform', - ...( ! ALLOW_REUSABLE_BLOCKS && [ 'core/block' ] ), + ...( ALLOW_REUSABLE_BLOCKS ? [] : [ 'core/block' ] ), ]; /** From 8013efd916b06bac142207983014673de026bfe1 Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco <gerardo.pacheco@automattic.com> Date: Wed, 8 Sep 2021 19:29:04 +0200 Subject: [PATCH 213/214] Mobile - Allow disabling text and background color via theme.json (#34633) * Mobile - Allow disabling text and background color via theme.json * Mobile - Global styles - Update test --- packages/block-editor/src/store/defaults.native.js | 6 ++++++ .../src/mobile/global-styles-context/test/utils.native.js | 2 ++ .../src/mobile/global-styles-context/utils.native.js | 2 ++ 3 files changed, 10 insertions(+) diff --git a/packages/block-editor/src/store/defaults.native.js b/packages/block-editor/src/store/defaults.native.js index e660d821784059..8e58d6bde851cd 100644 --- a/packages/block-editor/src/store/defaults.native.js +++ b/packages/block-editor/src/store/defaults.native.js @@ -13,6 +13,12 @@ const SETTINGS_DEFAULTS = { __unstableGalleryWithImageBlocks: __DEV__, alignWide: true, supportsLayout: false, + __experimentalFeatures: { + color: { + text: true, + background: true, + }, + }, }; export { PREFERENCES_DEFAULTS, SETTINGS_DEFAULTS }; diff --git a/packages/components/src/mobile/global-styles-context/test/utils.native.js b/packages/components/src/mobile/global-styles-context/test/utils.native.js index 6a7f219118777a..3aaf7efe7f977c 100644 --- a/packages/components/src/mobile/global-styles-context/test/utils.native.js +++ b/packages/components/src/mobile/global-styles-context/test/utils.native.js @@ -131,6 +131,8 @@ describe( 'getGlobalStyles', () => { color: { palette: RAW_FEATURES.color.palette, gradients, + text: true, + background: true, }, typography: { fontSizes: RAW_FEATURES.typography.fontSizes, diff --git a/packages/components/src/mobile/global-styles-context/utils.native.js b/packages/components/src/mobile/global-styles-context/utils.native.js index ed65c255c8f264..af4ed855983e97 100644 --- a/packages/components/src/mobile/global-styles-context/utils.native.js +++ b/packages/components/src/mobile/global-styles-context/utils.native.js @@ -273,6 +273,8 @@ export function getGlobalStyles( rawStyles, rawFeatures ) { color: { palette: colors?.palette, gradients, + text: features?.color?.text ?? true, + background: features?.color?.background ?? true, }, typography: { fontSizes: features?.typography?.fontSizes, From 126eb8ae9f3662e8d9d5403b2058ee14a3ad372e Mon Sep 17 00:00:00 2001 From: Vicente Canales <1157901+vcanales@users.noreply.github.com> Date: Wed, 8 Sep 2021 16:52:30 -0300 Subject: [PATCH 214/214] Fix snackbar overflow on nav editor (#34661) --- packages/edit-navigation/src/components/notices/style.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/edit-navigation/src/components/notices/style.scss b/packages/edit-navigation/src/components/notices/style.scss index c952988f1a09a1..a633a2af3bc5e2 100644 --- a/packages/edit-navigation/src/components/notices/style.scss +++ b/packages/edit-navigation/src/components/notices/style.scss @@ -1,7 +1,7 @@ .edit-navigation-notices__snackbar-list { position: fixed; - bottom: 20px; - margin-left: 20px; + bottom: 0; + padding: 20px; } .edit-navigation-notices__notice-list {