From a3c50d60998db8e1eaba1019e217524c23767d57 Mon Sep 17 00:00:00 2001 From: Haz Date: Thu, 24 Sep 2020 15:03:29 -0300 Subject: [PATCH 01/79] Fix keyboard navigation on the Image block toolbar (#25127) * Fix keyboard navigation on the Image block toolbar * Add toggleProps prop to AspectMenu component * Use ToolbarItem on TemplateParts * Revert "Use ToolbarItem on TemplateParts" This reverts commit dfde4c750251e1ff6fe9fda66212d1589a96970c. * Ignore warnings on TemplateParts block * Fix styles * Use ToolbarGroup on TemplateParts * Remove unnecessary styles --- .../src/components/navigable-toolbar/index.js | 14 +------------- packages/block-library/src/image/image-editor.js | 9 ++++++++- .../block-library/src/template-part/edit/index.js | 6 +++--- .../block-library/src/template-part/editor.scss | 5 ----- packages/components/src/toolbar-group/style.scss | 7 ++++++- .../specs/experiments/multi-entity-saving.test.js | 5 +++++ .../specs/experiments/template-part.test.js | 10 ++++++++++ 7 files changed, 33 insertions(+), 23 deletions(-) diff --git a/packages/block-editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js index 931932b7ab6da..5b3fad6e09138 100644 --- a/packages/block-editor/src/components/navigable-toolbar/index.js +++ b/packages/block-editor/src/components/navigable-toolbar/index.js @@ -13,16 +13,6 @@ import deprecated from '@wordpress/deprecated'; import { focus } from '@wordpress/dom'; import { useShortcut } from '@wordpress/keyboard-shortcuts'; -function useUpdateLayoutEffect( effect, deps ) { - const mounted = useRef( false ); - useLayoutEffect( () => { - if ( mounted.current ) { - return effect(); - } - mounted.current = true; - }, deps ); -} - function hasOnlyToolbarItem( elements ) { const dataProp = 'toolbarItem'; return ! elements.some( ( element ) => ! ( dataProp in element.dataset ) ); @@ -71,9 +61,7 @@ function useIsAccessibleToolbar( ref ) { setIsAccessibleToolbar( onlyToolbarItem ); }, [] ); - useLayoutEffect( determineIsAccessibleToolbar, [] ); - - useUpdateLayoutEffect( () => { + useLayoutEffect( () => { // Toolbar buttons may be rendered asynchronously, so we use // MutationObserver to check if the toolbar subtree has been modified const observer = new window.MutationObserver( diff --git a/packages/block-library/src/image/image-editor.js b/packages/block-library/src/image/image-editor.js index 194ed83ad53f9..a3401132390fb 100644 --- a/packages/block-library/src/image/image-editor.js +++ b/packages/block-library/src/image/image-editor.js @@ -60,12 +60,19 @@ function AspectGroup( { aspectRatios, isDisabled, label, onClick, value } ) { ); } -function AspectMenu( { isDisabled, onClick, value, defaultValue } ) { +function AspectMenu( { + toggleProps, + isDisabled, + onClick, + value, + defaultValue, +} ) { return ( { ( { onClose } ) => ( diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js index c6936a83c5e56..649c82539d9f9 100644 --- a/packages/block-library/src/template-part/edit/index.js +++ b/packages/block-library/src/template-part/edit/index.js @@ -7,7 +7,7 @@ import { BlockControls, __experimentalBlock as Block, } from '@wordpress/block-editor'; -import { Dropdown, ToolbarButton } from '@wordpress/components'; +import { Dropdown, ToolbarGroup, ToolbarButton } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { chevronUp, chevronDown } from '@wordpress/icons'; @@ -71,7 +71,7 @@ export default function TemplatePartEdit( { return ( -
+ ) } /> -
+
{ await page.keyboard.type( 'some words...' ); await assertMultiSaveEnabled(); + + // TODO: Remove when toolbar supports text fields + expect( console ).toHaveWarnedWith( + 'Using custom components as toolbar controls is deprecated. Please use ToolbarItem or ToolbarButton components instead. See: https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols' + ); } ); it( 'Should only have save panel a11y button active after child entities edited', async () => { diff --git a/packages/e2e-tests/specs/experiments/template-part.test.js b/packages/e2e-tests/specs/experiments/template-part.test.js index 835ea93a0a7c0..f18f13e3e8724 100644 --- a/packages/e2e-tests/specs/experiments/template-part.test.js +++ b/packages/e2e-tests/specs/experiments/template-part.test.js @@ -116,6 +116,11 @@ describe( 'Template Part', () => { await page.keyboard.type( testContent ); await page.click( savePostSelector ); await page.click( entitiesSaveSelector ); + + // TODO: Remove when toolbar supports text fields + expect( console ).toHaveWarnedWith( + 'Using custom components as toolbar controls is deprecated. Please use ToolbarItem or ToolbarButton components instead. See: https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols' + ); } ); it( 'Should preview newly added template part', async () => { @@ -138,6 +143,11 @@ describe( 'Template Part', () => { testContentSelector ); expect( templatePartContent ).toBeTruthy(); + + // TODO: Remove when toolbar supports text fields + expect( console ).toHaveWarnedWith( + 'Using custom components as toolbar controls is deprecated. Please use ToolbarItem or ToolbarButton components instead. See: https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols' + ); } ); } ); } ); From d220d6c5d666daa5154c638b2e7fd8dc9c30e684 Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Thu, 24 Sep 2020 13:06:42 -0500 Subject: [PATCH 02/79] [RNMobile] Improve AztecWrapper RN talkback support (#25384) * updated aztec to temporary commit until the PR is merged. * Made the AztecText view enabled and focusable to activate TalkBack func --- packages/react-native-aztec/android/build.gradle | 2 +- .../wordpress/mobile/ReactNativeAztec/ReactAztecManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-aztec/android/build.gradle b/packages/react-native-aztec/android/build.gradle index 8de126d6d4aa6..987dcb784bd3f 100644 --- a/packages/react-native-aztec/android/build.gradle +++ b/packages/react-native-aztec/android/build.gradle @@ -11,7 +11,7 @@ buildscript { jSoupVersion = '1.10.3' wordpressUtilsVersion = '1.22' espressoVersion = '3.0.1' - aztecVersion = 'v1.3.44' + aztecVersion = 'b8fa76f10346f6e8b979697154d3680f96cb79ff' } repositories { 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 b52b4be20af15..7d39098ae8c80 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 @@ -108,7 +108,7 @@ public String getName() { protected ReactAztecText createViewInstance(ThemedReactContext reactContext) { ReactAztecText aztecText = new ReactAztecText(reactContext); aztecText.setFocusableInTouchMode(false); - aztecText.setFocusable(false); + aztecText.setEnabled(true); aztecText.setCalypsoMode(false); aztecText.setPadding(0, 0, 0, 0); // This is a temporary hack that sets the correct GB link color and underline From e4d5da19077e1f4921d628e41de70b1ec7ebcaf7 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 24 Sep 2020 21:25:29 +0100 Subject: [PATCH 03/79] Add: Reset button to global styles sidebar (#25426) --- .../editor/global-styles-provider.js | 22 ++++++++++++++---- .../src/components/sidebar/default-sidebar.js | 12 +++++++++- .../sidebar/global-styles-sidebar.js | 23 +++++++++++++++++-- .../src/components/sidebar/style.scss | 8 +++++++ 4 files changed, 57 insertions(+), 8 deletions(-) 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 193b0bd8c30ad..e1b85a934f873 100644 --- a/packages/edit-site/src/components/editor/global-styles-provider.js +++ b/packages/edit-site/src/components/editor/global-styles-provider.js @@ -8,6 +8,7 @@ import { set, get } from 'lodash'; */ import { createContext, + useCallback, useContext, useEffect, useMemo, @@ -20,6 +21,8 @@ import { __EXPERIMENTAL_STYLE_PROPERTY as STYLE_PROPERTY } from '@wordpress/bloc */ import getGlobalStyles from './global-styles-renderer'; +const EMPTY_CONTENT = '{}'; + const GlobalStylesContext = createContext( { /* eslint-disable no-unused-vars */ getStyleProperty: ( context, propertyName ) => {}, @@ -30,12 +33,21 @@ const GlobalStylesContext = createContext( { export const useGlobalStylesContext = () => useContext( GlobalStylesContext ); +const useGlobalStylesEntityContent = () => { + return useEntityProp( 'postType', 'wp_global_styles', 'content' ); +}; + +export const useGlobalStylesReset = () => { + const [ content, setContent ] = useGlobalStylesEntityContent(); + const canRestart = !! content && content !== EMPTY_CONTENT; + return [ + canRestart, + useCallback( () => setContent( EMPTY_CONTENT ), [ setContent ] ), + ]; +}; + export default ( { children, baseStyles, contexts } ) => { - const [ content, setContent ] = useEntityProp( - 'postType', - 'wp_global_styles', - 'content' - ); + const [ content, setContent ] = useGlobalStylesEntityContent(); const userStyles = useMemo( () => ( content ? JSON.parse( content ) : {} ), diff --git a/packages/edit-site/src/components/sidebar/default-sidebar.js b/packages/edit-site/src/components/sidebar/default-sidebar.js index ac3b699b6e278..2f8c21202fb0d 100644 --- a/packages/edit-site/src/components/sidebar/default-sidebar.js +++ b/packages/edit-site/src/components/sidebar/default-sidebar.js @@ -6,15 +6,25 @@ import { ComplementaryAreaMoreMenuItem, } from '@wordpress/interface'; -export default ( { identifier, title, icon, children, closeLabel } ) => { +export default ( { + className, + identifier, + title, + icon, + children, + closeLabel, + header, +} ) => { return ( <> { children } diff --git a/packages/edit-site/src/components/sidebar/global-styles-sidebar.js b/packages/edit-site/src/components/sidebar/global-styles-sidebar.js index c68ff0df7ff27..1f140af3ee78b 100644 --- a/packages/edit-site/src/components/sidebar/global-styles-sidebar.js +++ b/packages/edit-site/src/components/sidebar/global-styles-sidebar.js @@ -6,14 +6,17 @@ import { omit } from 'lodash'; /** * WordPress dependencies */ -import { PanelBody, TabPanel } from '@wordpress/components'; +import { Button, PanelBody, TabPanel } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { getBlockType } from '@wordpress/blocks'; /** * Internal dependencies */ -import { useGlobalStylesContext } from '../editor/global-styles-provider'; +import { + useGlobalStylesContext, + useGlobalStylesReset, +} from '../editor/global-styles-provider'; import DefaultSidebar from './default-sidebar'; import { GLOBAL_CONTEXT } from '../editor/utils'; import TypographyPanel from './typography-panel'; @@ -25,6 +28,7 @@ export default ( { identifier, title, icon, closeLabel } ) => { getStyleProperty, setStyleProperty, } = useGlobalStylesContext(); + const [ canRestart, onReset ] = useGlobalStylesReset(); if ( typeof contexts !== 'object' || ! contexts?.[ GLOBAL_CONTEXT ] ) { // No sidebar is shown. @@ -33,10 +37,25 @@ export default ( { identifier, title, icon, closeLabel } ) => { return ( + { title } + + + } > Date: Fri, 25 Sep 2020 00:46:29 +0300 Subject: [PATCH 04/79] Block API: deprecate experimental Block.* component (#25515) * Block API: deprecate experimental Block.* component * Revert complex innerblock case * Address feedback * Adjust group block * Fix innerblocks issue * Fix template part block * Add spinner --- .../components/block-list/block-wrapper.js | 14 ++--- .../src/components/block-list/block.js | 24 +++++--- .../src/components/block-list/index.js | 2 +- packages/block-library/src/column/edit.js | 17 +++--- packages/block-library/src/columns/edit.js | 55 ++++++++++--------- packages/block-library/src/group/edit.js | 15 +++-- packages/block-library/src/heading/edit.js | 15 +++-- packages/block-library/src/post-title/edit.js | 19 +++---- .../block-library/src/preformatted/edit.js | 10 ++-- .../block-library/src/site-tagline/edit.js | 6 +- .../src/site-title/edit/index.js | 13 +++-- .../block-library/src/social-links/edit.js | 6 +- .../src/template-part/edit/index.js | 50 ++++++++++------- .../src/template-part/edit/inner-blocks.js | 5 +- packages/block-library/src/verse/edit.js | 14 +++-- 15 files changed, 145 insertions(+), 120 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block-wrapper.js b/packages/block-editor/src/components/block-list/block-wrapper.js index b4a2885524fb4..4aef321a0bf20 100644 --- a/packages/block-editor/src/components/block-list/block-wrapper.js +++ b/packages/block-editor/src/components/block-list/block-wrapper.js @@ -18,6 +18,7 @@ import { focus, isTextField, placeCaretAtHorizontalEdge } from '@wordpress/dom'; import { ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes'; import { __, sprintf } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -286,14 +287,11 @@ export function useBlockWrapperProps( props = {}, { __unstableIsHtml } = {} ) { } const BlockComponent = forwardRef( - ( - { children, tagName: TagName = 'div', __unstableIsHtml, ...props }, - ref - ) => { - const blockWrapperProps = useBlockWrapperProps( - { ...props, ref }, - { __unstableIsHtml } - ); + ( { children, tagName: TagName = 'div', ...props }, ref ) => { + deprecated( 'wp.blockEditor.__experimentalBlock', { + alternative: 'wp.blockEditor.__experimentalUseBlockWrapperProps', + } ); + const blockWrapperProps = useBlockWrapperProps( { ...props, ref } ); return { children }; } ); diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 06c54b09bfddb..345005caf1daa 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -40,7 +40,7 @@ import BlockInvalidWarning from './block-invalid-warning'; import BlockCrashWarning from './block-crash-warning'; import BlockCrashBoundary from './block-crash-boundary'; import BlockHtml from './block-html'; -import { Block } from './block-wrapper'; +import { useBlockWrapperProps } from './block-wrapper'; export const BlockListBlockContext = createContext(); @@ -68,6 +68,14 @@ function mergeWrapperProps( propsA, propsB ) { return newProps; } +function Block( { children, isHtml, ...props } ) { + return ( +
+ { children } +
+ ); +} + function BlockListBlock( { mode, isFocusMode, @@ -219,10 +227,10 @@ function BlockListBlock( { if ( ! isValid ) { block = ( - +
{ getSaveElement( blockType, attributes ) }
-
+ ); } else if ( mode === 'html' ) { // Render blockEdit so the inspector controls don't disappear. @@ -230,15 +238,15 @@ function BlockListBlock( { block = ( <>
{ blockEdit }
- + - + ); } else if ( lightBlockWrapper ) { block = blockEdit; } else { - block = { blockEdit }; + block = { blockEdit }; } return ( @@ -247,9 +255,9 @@ function BlockListBlock( { { block } { !! hasError && ( - + - + ) } ); diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index b1ce89ab83e9d..a4dc285582a6c 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -80,8 +80,8 @@ function BlockList( return ( @@ -83,15 +87,10 @@ function ColumnEdit( { + hasChildBlocks ? undefined : InnerBlocks.ButtonBlockAppender } - __experimentalTagName={ Block.div } - __experimentalPassedProps={ { - className: classes, - style: hasWidth ? { flexBasis: width + '%' } : undefined, - } } + __experimentalTagName="div" + __experimentalPassedProps={ blockWrapperProps } /> ); diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index 23f5e4e2bc7a8..9b049aa47679f 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -16,7 +16,7 @@ import { BlockControls, BlockVerticalAlignmentToolbar, __experimentalBlockVariationPicker, - __experimentalBlock as Block, + __experimentalUseBlockWrapperProps as useBlockWrapperProps, } from '@wordpress/block-editor'; import { withDispatch, useDispatch, useSelect } from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; @@ -63,6 +63,10 @@ function ColumnsEditContainer( { [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); + const blockWrapperProps = useBlockWrapperProps( { + className: classes, + } ); + return ( <> @@ -92,10 +96,8 @@ function ColumnsEditContainer( { @@ -208,14 +210,8 @@ const createBlocksFromInnerBlocksTemplate = ( innerBlocksTemplate ) => { ); }; -const ColumnsEdit = ( props ) => { - const { clientId, name } = props; - const { - blockType, - defaultVariation, - hasInnerBlocks, - variations, - } = useSelect( +function Placeholder( { clientId, name, setAttributes } ) { + const { blockType, defaultVariation, variations } = useSelect( ( select ) => { const { getBlockVariations, @@ -226,34 +222,27 @@ const ColumnsEdit = ( props ) => { return { blockType: getBlockType( name ), defaultVariation: getDefaultBlockVariation( name, 'block' ), - hasInnerBlocks: - select( 'core/block-editor' ).getBlocks( clientId ).length > - 0, variations: getBlockVariations( name, 'block' ), }; }, - [ clientId, name ] + [ name ] ); - const { replaceInnerBlocks } = useDispatch( 'core/block-editor' ); - - if ( hasInnerBlocks ) { - return ; - } + const blockWrapperProps = useBlockWrapperProps(); return ( - +
<__experimentalBlockVariationPicker icon={ get( blockType, [ 'icon', 'src' ] ) } label={ get( blockType, [ 'title' ] ) } variations={ variations } onSelect={ ( nextVariation = defaultVariation ) => { if ( nextVariation.attributes ) { - props.setAttributes( nextVariation.attributes ); + setAttributes( nextVariation.attributes ); } if ( nextVariation.innerBlocks ) { replaceInnerBlocks( - props.clientId, + clientId, createBlocksFromInnerBlocksTemplate( nextVariation.innerBlocks ) @@ -262,8 +251,22 @@ const ColumnsEdit = ( props ) => { } } allowSkip /> - +
); +} + +const ColumnsEdit = ( props ) => { + const { clientId } = props; + const hasInnerBlocks = useSelect( + ( select ) => + select( 'core/block-editor' ).getBlocks( clientId ).length > 0, + [ clientId ] + ); + const Component = hasInnerBlocks + ? ColumnsEditContainerWrapper + : Placeholder; + + return ; }; export default ColumnsEdit; diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index 9397662e67d77..975421e7d5ac6 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -4,12 +4,12 @@ import { useSelect } from '@wordpress/data'; import { InnerBlocks, - __experimentalBlock as Block, + __experimentalUseBlockWrapperProps as useBlockWrapperProps, } from '@wordpress/block-editor'; import { __experimentalBoxControl as BoxControl } from '@wordpress/components'; const { __Visualizer: BoxControlVisualizer } = BoxControl; -function GroupEdit( { attributes, className, clientId } ) { +function GroupEdit( { attributes, clientId } ) { const hasInnerBlocks = useSelect( ( select ) => { const { getBlock } = select( 'core/block-editor' ); @@ -18,26 +18,25 @@ function GroupEdit( { attributes, className, clientId } ) { }, [ clientId ] ); - const BlockWrapper = Block[ attributes.tagName ]; + const blockWrapperProps = useBlockWrapperProps(); + const { tagName: TagName = 'div' } = attributes; return ( - + + hasInnerBlocks ? undefined : InnerBlocks.ButtonBlockAppender } __experimentalTagName="div" __experimentalPassedProps={ { className: 'wp-block-group__inner-container', } } /> - + ); } diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index d4a353c1b0ead..9c8adc25f3fba 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -12,7 +12,7 @@ import { AlignmentToolbar, BlockControls, RichText, - __experimentalBlock as Block, + __experimentalUseBlockWrapperProps as useBlockWrapperProps, } from '@wordpress/block-editor'; import { ToolbarGroup } from '@wordpress/components'; @@ -30,6 +30,12 @@ function HeadingEdit( { } ) { const { align, content, level, placeholder } = attributes; const tagName = 'h' + level; + const blockWrapperProps = useBlockWrapperProps( { + className: classnames( { + [ `has-text-align-${ align }` ]: align, + } ), + style: mergedStyle, + } ); return ( <> @@ -51,7 +57,7 @@ function HeadingEdit( {
setAttributes( { content: value } ) } onMerge={ mergeBlocks } @@ -67,12 +73,9 @@ function HeadingEdit( { } } onReplace={ onReplace } onRemove={ () => onReplace( [] ) } - className={ classnames( { - [ `has-text-align-${ align }` ]: align, - } ) } placeholder={ placeholder || __( 'Write heading…' ) } textAlign={ align } - style={ mergedStyle } + { ...blockWrapperProps } /> ); diff --git a/packages/block-library/src/post-title/edit.js b/packages/block-library/src/post-title/edit.js index 44903efc5bece..0d86af1c6e080 100644 --- a/packages/block-library/src/post-title/edit.js +++ b/packages/block-library/src/post-title/edit.js @@ -11,7 +11,7 @@ import { AlignmentToolbar, BlockControls, InspectorControls, - __experimentalBlock as Block, + __experimentalUseBlockWrapperProps as useBlockWrapperProps, } from '@wordpress/block-editor'; import { ToolbarGroup, @@ -31,7 +31,7 @@ export default function PostTitleEdit( { setAttributes, context: { postType, postId }, } ) { - const tagName = 0 === level ? 'p' : 'h' + level; + const TagName = 0 === level ? 'p' : 'h' + level; const post = useSelect( ( select ) => @@ -43,11 +43,16 @@ export default function PostTitleEdit( { [ postType, postId ] ); + const blockWrapperProps = useBlockWrapperProps( { + className: classnames( { + [ `has-text-align-${ textAlign }` ]: textAlign, + } ), + } ); + if ( ! post ) { return null; } - const BlockWrapper = Block[ tagName ]; let title = post.title || __( 'Post Title' ); if ( isLink ) { title = ( @@ -104,13 +109,7 @@ export default function PostTitleEdit( { ) } - - { title } - + { title } ); } diff --git a/packages/block-library/src/preformatted/edit.js b/packages/block-library/src/preformatted/edit.js index 0acc7f26ae6c2..3fef1a707fb7b 100644 --- a/packages/block-library/src/preformatted/edit.js +++ b/packages/block-library/src/preformatted/edit.js @@ -4,21 +4,20 @@ import { __ } from '@wordpress/i18n'; import { RichText, - __experimentalBlock as Block, + __experimentalUseBlockWrapperProps as useBlockWrapperProps, } from '@wordpress/block-editor'; export default function PreformattedEdit( { attributes, mergeBlocks, setAttributes, - className, - style, } ) { const { content } = attributes; + const blockWrapperProps = useBlockWrapperProps(); return ( ); } diff --git a/packages/block-library/src/site-tagline/edit.js b/packages/block-library/src/site-tagline/edit.js index b7ee2737dca89..be5b1582911fd 100644 --- a/packages/block-library/src/site-tagline/edit.js +++ b/packages/block-library/src/site-tagline/edit.js @@ -9,7 +9,7 @@ import classnames from 'classnames'; import { useEntityProp } from '@wordpress/core-data'; import { AlignmentToolbar, - __experimentalBlock as Block, + __experimentalUseBlockWrapperProps as useBlockWrapperProps, BlockControls, RichText, } from '@wordpress/block-editor'; @@ -22,6 +22,7 @@ export default function SiteTaglineEdit( { attributes, setAttributes } ) { 'site', 'description' ); + const blockWrapperProps = useBlockWrapperProps(); return ( <> @@ -41,8 +42,9 @@ export default function SiteTaglineEdit( { attributes, setAttributes } ) { } ) } onChange={ setSiteTagline } placeholder={ __( 'Site Tagline' ) } - tagName={ Block.p } + tagName="p" value={ siteTagline } + { ...blockWrapperProps } /> ); diff --git a/packages/block-library/src/site-title/edit/index.js b/packages/block-library/src/site-title/edit/index.js index 3079ced37d6a6..772dcc0646e47 100644 --- a/packages/block-library/src/site-title/edit/index.js +++ b/packages/block-library/src/site-title/edit/index.js @@ -12,7 +12,7 @@ import { RichText, AlignmentToolbar, BlockControls, - __experimentalBlock as Block, + __experimentalUseBlockWrapperProps as useBlockWrapperProps, } from '@wordpress/block-editor'; /** @@ -24,6 +24,11 @@ export default function SiteTitleEdit( { attributes, setAttributes } ) { const { level, textAlign } = attributes; const [ title, setTitle ] = useEntityProp( 'root', 'site', 'title' ); const tagName = level === 0 ? 'p' : `h${ level }`; + const blockWrapperProps = useBlockWrapperProps( { + className: classnames( { + [ `has-text-align-${ textAlign }` ]: textAlign, + } ), + } ); return ( <> @@ -44,15 +49,13 @@ export default function SiteTitleEdit( { attributes, setAttributes } ) { ); diff --git a/packages/block-library/src/social-links/edit.js b/packages/block-library/src/social-links/edit.js index 5e17cb2b5ee8f..8df3d0da0304e 100644 --- a/packages/block-library/src/social-links/edit.js +++ b/packages/block-library/src/social-links/edit.js @@ -6,7 +6,7 @@ import { Fragment } from '@wordpress/element'; import { InnerBlocks, - __experimentalBlock as Block, + __experimentalUseBlockWrapperProps as useBlockWrapperProps, InspectorControls, } from '@wordpress/block-editor'; import { ToggleControl, PanelBody } from '@wordpress/components'; @@ -32,6 +32,7 @@ export function SocialLinksEdit( props ) { attributes: { openInNewTab }, setAttributes, } = props; + const blockWrapperProps = useBlockWrapperProps(); return ( @@ -50,7 +51,8 @@ export function SocialLinksEdit( props ) { templateLock={ false } template={ TEMPLATE } orientation="horizontal" - __experimentalTagName={ Block.ul } + __experimentalTagName="ul" + __experimentalPassedProps={ blockWrapperProps } __experimentalAppenderTagName="li" /> diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js index 649c82539d9f9..8c93ed57e172d 100644 --- a/packages/block-library/src/template-part/edit/index.js +++ b/packages/block-library/src/template-part/edit/index.js @@ -5,9 +5,14 @@ import { useRef, useEffect } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { BlockControls, - __experimentalBlock as Block, + __experimentalUseBlockWrapperProps as useBlockWrapperProps, } from '@wordpress/block-editor'; -import { Dropdown, ToolbarGroup, ToolbarButton } from '@wordpress/components'; +import { + Dropdown, + ToolbarGroup, + ToolbarButton, + Spinner, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { chevronUp, chevronDown } from '@wordpress/icons'; @@ -21,7 +26,7 @@ import TemplatePartPlaceholder from './placeholder'; import TemplatePartSelection from './selection'; export default function TemplatePartEdit( { - attributes: { postId: _postId, slug, theme, tagName }, + attributes: { postId: _postId, slug, theme, tagName: TagName = 'div' }, setAttributes, clientId, } ) { @@ -64,12 +69,22 @@ export default function TemplatePartEdit( { } }, [ innerBlocks ] ); - const BlockWrapper = Block[ tagName ]; + const blockWrapperProps = useBlockWrapperProps(); + + // Part of a template file, post ID already resolved. + const isTemplateFile = !! postId; + // Fresh new block. + const isPlaceholder = + ! postId && ! initialSlug.current && ! initialTheme.current; + // Part of a template file, post ID not resolved yet. + const isUnresolvedTemplateFile = ! isPlaceholder && ! postId; - if ( postId ) { - // Part of a template file, post ID already resolved. - return ( - + return ( + + { isPlaceholder && ( + + ) } + { isTemplateFile && ( + ) } + { isTemplateFile && ( 0 } /> - - ); - } - if ( ! initialSlug.current && ! initialTheme.current ) { - // Fresh new block. - return ( - - - - ); - } - // Part of a template file, post ID not resolved yet. - return null; + ) } + { isUnresolvedTemplateFile && } + + ); } diff --git a/packages/block-library/src/template-part/edit/inner-blocks.js b/packages/block-library/src/template-part/edit/inner-blocks.js index fa26416f125d3..a2295a3de6eb4 100644 --- a/packages/block-library/src/template-part/edit/inner-blocks.js +++ b/packages/block-library/src/template-part/edit/inner-blocks.js @@ -4,7 +4,6 @@ import { useEntityBlockEditor } from '@wordpress/core-data'; import { InnerBlocks } from '@wordpress/block-editor'; -const renderAppender = () => ; export default function TemplatePartInnerBlocks( { postId: id, hasInnerBlocks, @@ -20,7 +19,9 @@ export default function TemplatePartInnerBlocks( { onInput={ onInput } onChange={ onChange } __experimentalTagName="div" - renderAppender={ hasInnerBlocks ? undefined : renderAppender } + renderAppender={ + hasInnerBlocks ? undefined : InnerBlocks.ButtonBlockAppender + } /> ); } diff --git a/packages/block-library/src/verse/edit.js b/packages/block-library/src/verse/edit.js index 25370e634eb75..fb6342fdd6155 100644 --- a/packages/block-library/src/verse/edit.js +++ b/packages/block-library/src/verse/edit.js @@ -11,16 +11,20 @@ import { RichText, BlockControls, AlignmentToolbar, - __experimentalBlock as Block, + __experimentalUseBlockWrapperProps as useBlockWrapperProps, } from '@wordpress/block-editor'; export default function VerseEdit( { attributes, setAttributes, - className, mergeBlocks, } ) { const { textAlign, content } = attributes; + const blockWrapperProps = useBlockWrapperProps( { + className: classnames( { + [ `has-text-align-${ textAlign }` ]: textAlign, + } ), + } ); return ( <> @@ -33,7 +37,7 @@ export default function VerseEdit( { /> ); From a68265dc2bd601a2af278ce48d23ed5e78edcfab Mon Sep 17 00:00:00 2001 From: andrei draganescu Date: Fri, 25 Sep 2020 06:26:24 +0300 Subject: [PATCH 05/79] Wrap navigation editing features with filters (#25329) * wrap navigation editing features with filters * Switch to using BlockEdit filter Change navigation block to use __experimentalColor support option, and disable feature on navigation screen Remove unused ref Refactor to use feature flags injected as props to BlockEdit Also remove font size support Remove CSS Class and anchor support on navigation screen Co-authored-by: Daniel Richards --- .../src/navigation/block-colors-selector.js | 92 ------- .../block-library/src/navigation/block.json | 11 +- packages/block-library/src/navigation/edit.js | 248 ++++++++---------- packages/edit-navigation/src/index.js | 52 +++- 4 files changed, 164 insertions(+), 239 deletions(-) delete mode 100644 packages/block-library/src/navigation/block-colors-selector.js diff --git a/packages/block-library/src/navigation/block-colors-selector.js b/packages/block-library/src/navigation/block-colors-selector.js deleted file mode 100644 index 802aa03225a15..0000000000000 --- a/packages/block-library/src/navigation/block-colors-selector.js +++ /dev/null @@ -1,92 +0,0 @@ -/** - * WordPress dependencies - */ -import { - ToolbarButton, - Dropdown, - ToolbarGroup, - SVG, - Path, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { DOWN } from '@wordpress/keycodes'; - -const ColorSelectorSVGIcon = () => ( - - - -); - -/** - * Color Selector Icon component. - * - * @param {Object} props Component properties. - * @param {Object} props.style Style object. - * @param {string} props.className Class name for component. - * - * @return {*} React Icon component. - */ -const ColorSelectorIcon = ( { style, className } ) => { - return ( -
-
- -
-
- ); -}; - -/** - * Renders the Colors Selector Toolbar with the icon button. - * - * @param {Object} props Component properties. - * @param {Object} props.TextColor Text color component that wraps icon. - * @param {Object} props.BackgroundColor Background color component that wraps icon. - * - * @return {*} React toggle button component. - */ -const renderToggleComponent = ( { TextColor, BackgroundColor } ) => ( { - onToggle, - isOpen, -} ) => { - const openOnArrowDown = ( event ) => { - if ( ! isOpen && event.keyCode === DOWN ) { - event.preventDefault(); - event.stopPropagation(); - onToggle(); - } - }; - - return ( - - - - - - - } - /> - - ); -}; - -const BlockColorsStyleSelector = ( { children, ...other } ) => ( - children } - /> -); - -export default BlockColorsStyleSelector; diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index bd93c30bc9101..d12d267cf26a1 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -41,11 +41,18 @@ "showSubmenuIcon": "showSubmenuIcon" }, "supports": { - "align": [ "wide", "full" ], + "align": [ + "wide", + "full" + ], "anchor": true, "html": false, "inserter": true, "lightBlockWrapper": true, - "__experimentalFontSize": true + "__experimentalFontSize": true, + "__experimentalColor": { + "textColor": true, + "backgroundColor": true + } } } diff --git a/packages/block-library/src/navigation/edit.js b/packages/block-library/src/navigation/edit.js index 6dbf24663abab..71f854c63f0ca 100644 --- a/packages/block-library/src/navigation/edit.js +++ b/packages/block-library/src/navigation/edit.js @@ -7,21 +7,15 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useRef, useState } from '@wordpress/element'; +import { useState } from '@wordpress/element'; import { InnerBlocks, InspectorControls, BlockControls, - __experimentalUseColors, __experimentalUseBlockWrapperProps as useBlockWrapperProps, } from '@wordpress/block-editor'; import { useDispatch, withSelect, withDispatch } from '@wordpress/data'; -import { - PanelBody, - ToggleControl, - Toolbar, - ToolbarGroup, -} from '@wordpress/components'; +import { PanelBody, ToggleControl, ToolbarGroup } from '@wordpress/components'; import { compose } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; @@ -29,26 +23,23 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import useBlockNavigator from './use-block-navigator'; -import BlockColorsStyleSelector from './block-colors-selector'; import * as navIcons from './icons'; import NavigationPlaceholder from './placeholder'; function Navigation( { selectedBlockHasDescendants, attributes, + setAttributes, clientId, hasExistingNavItems, isImmediateParentOfSelectedBlock, isSelected, - setAttributes, updateInnerBlocks, className, + hasSubmenuIndicatorSetting = true, + hasItemJustificationControls = true, + hasListViewModal = true, } ) { - // - // HOOKS - // - const ref = useRef(); - const [ isPlaceholderShown, setIsPlaceholderShown ] = useState( ! hasExistingNavItems ); @@ -56,52 +47,14 @@ function Navigation( { const { selectBlock } = useDispatch( 'core/block-editor' ); const blockProps = useBlockWrapperProps(); - - const { TextColor, BackgroundColor, ColorPanel } = __experimentalUseColors( - [ - { name: 'textColor', property: 'color' }, - { name: 'backgroundColor', className: 'has-background' }, - ], - { - contrastCheckers: [ - { - backgroundColor: true, - textColor: true, - }, - ], - colorDetector: { targetRef: ref }, - colorPanelProps: { - initialOpen: true, - }, - } - ); - const { navigatorToolbarButton, navigatorModal } = useBlockNavigator( clientId ); - // - // HANDLERS - // - function handleItemsAlignment( align ) { - return () => { - const itemsJustification = - attributes.itemsJustification === align ? undefined : align; - setAttributes( { - itemsJustification, - } ); - }; - } - - // - // RENDER - // - if ( isPlaceholderShown ) { return (
{ setIsPlaceholderShown( false ); updateInnerBlocks( blocks ); @@ -114,6 +67,16 @@ function Navigation( { ); } + function handleItemsAlignment( align ) { + return () => { + const itemsJustification = + attributes.itemsJustification === align ? undefined : align; + setAttributes( { + itemsJustification, + } ); + }; + } + const blockClassNames = classnames( className, { [ `items-justified-${ attributes.itemsJustification }` ]: attributes.itemsJustification, 'is-vertical': attributes.orientation === 'vertical', @@ -122,101 +85,98 @@ function Navigation( { return ( <> - - { navigatorToolbarButton } - - { ColorPanel } - + { hasItemJustificationControls && ( + + ) } + { hasListViewModal && ( + { navigatorToolbarButton } + ) } - { navigatorModal } + { hasListViewModal && navigatorModal } - - { - setAttributes( { showSubmenuIcon: value } ); - } } - label={ __( 'Show submenu indicator icons' ) } - /> - - - - - - - + + ) } + + ); } diff --git a/packages/edit-navigation/src/index.js b/packages/edit-navigation/src/index.js index bc858e2e964a6..db23acc1945f9 100644 --- a/packages/edit-navigation/src/index.js +++ b/packages/edit-navigation/src/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { map, set, flatten, partialRight } from 'lodash'; +import { map, set, flatten, omit, partialRight } from 'lodash'; /** * WordPress dependencies @@ -12,6 +12,7 @@ import { __experimentalRegisterExperimentalCoreBlocks, } from '@wordpress/block-library'; import { render } from '@wordpress/element'; +import { createHigherOrderComponent } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; @@ -31,6 +32,43 @@ function disableInsertingNonNavigationBlocks( settings, name ) { return settings; } +function removeNavigationBlockSettingsUnsupportedFeatures( settings, name ) { + if ( name !== 'core/navigation' ) { + return settings; + } + + return { + ...settings, + supports: { + ...omit( settings.supports, [ + 'anchor', + 'customClassName', + '__experimentalColor', + '__experimentalFontSize', + ] ), + customClassName: false, + }, + }; +} + +const removeNavigationBlockEditUnsupportedFeatures = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + if ( props.name !== 'core/navigation' ) { + return ; + } + + return ( + + ); + }, + 'removeNavigationBlockEditUnsupportedFeatures' +); + /** * Fetches link suggestions from the API. This function is an exact copy of a function found at: * @@ -115,6 +153,18 @@ export function initialize( id, settings ) { ); } + addFilter( + 'blocks.registerBlockType', + 'core/edit-navigation/remove-navigation-block-settings-unsupported-features', + removeNavigationBlockSettingsUnsupportedFeatures + ); + + addFilter( + 'editor.BlockEdit', + 'core/edit-navigation/remove-navigation-block-edit-unsupported-features', + removeNavigationBlockEditUnsupportedFeatures + ); + registerCoreBlocks(); if ( process.env.GUTENBERG_PHASE === 2 ) { From 39a424b912f903ea090574278c6f52b5d80f5022 Mon Sep 17 00:00:00 2001 From: etoledom Date: Fri, 25 Sep 2020 08:17:04 +0200 Subject: [PATCH 06/79] Fix `npm run native ios` with xcode12 Command Line Tools (#25532) * Fix `npm run native ios` with xcode12 Command Line Tools * Replace instances of GutenbergDemo with gutenberg --- .github/workflows/rnmobile-ios-runner.yml | 6 +++--- .../__device-tests__/helpers/utils.js | 2 +- .../ios/gutenberg.xcodeproj/project.pbxproj | 10 +++++----- .../xcshareddata/xcschemes/gutenberg.xcscheme | 8 ++++---- packages/react-native-editor/package.json | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index 7a7a69a14d564..840e49a55ee07 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -30,7 +30,7 @@ jobs: - name: Restore build cache uses: actions/cache@v2 with: - path: packages/react-native-editor/ios/build/gutenberg/Build/Products/Release-iphonesimulator/GutenbergDemo.app + path: packages/react-native-editor/ios/build/gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app key: ${{ runner.os }}-ios-build-${{ hashFiles('ios-checksums.txt') }} - name: Restore pods cache @@ -55,13 +55,13 @@ jobs: run: sudo xcode-select --switch /Applications/Xcode_11.4.1.app - name: Build (if needed) - run: test -e packages/react-native-editor/ios/build/gutenberg/Build/Products/Release-iphonesimulator/GutenbergDemo.app/GutenbergDemo || SKIP_BUNDLING=true npm run native test:e2e:build-app:ios + run: test -e packages/react-native-editor/ios/build/gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app/gutenberg || SKIP_BUNDLING=true npm run native test:e2e:build-app:ios - name: Run iOS Device Tests run: TEST_RN_PLATFORM=ios npm run native device-tests:local ${{ matrix.native-test-name }} - name: Prepare build cache - run: rm packages/react-native-editor/ios/build/gutenberg/Build/Products/Release-iphonesimulator/GutenbergDemo.app/main.jsbundle + run: rm packages/react-native-editor/ios/build/gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app/main.jsbundle - uses: actions/upload-artifact@v2 if: always() diff --git a/packages/react-native-editor/__device-tests__/helpers/utils.js b/packages/react-native-editor/__device-tests__/helpers/utils.js index 878b0cf57f503..eeb745c57d137 100644 --- a/packages/react-native-editor/__device-tests__/helpers/utils.js +++ b/packages/react-native-editor/__device-tests__/helpers/utils.js @@ -29,7 +29,7 @@ const testEnvironment = process.env.TEST_ENV || defaultEnvironment; const defaultAndroidAppPath = './android/app/build/outputs/apk/debug/app-debug.apk'; const defaultIOSAppPath = - './ios/build/gutenberg/Build/Products/Release-iphonesimulator/GutenbergDemo.app'; + './ios/build/gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app'; const localAndroidAppPath = process.env.ANDROID_APP_PATH || defaultAndroidAppPath; diff --git a/packages/react-native-editor/ios/gutenberg.xcodeproj/project.pbxproj b/packages/react-native-editor/ios/gutenberg.xcodeproj/project.pbxproj index b5cb62934183a..63a1b69eea849 100644 --- a/packages/react-native-editor/ios/gutenberg.xcodeproj/project.pbxproj +++ b/packages/react-native-editor/ios/gutenberg.xcodeproj/project.pbxproj @@ -57,7 +57,7 @@ 034874811302E030BA1637A1 /* Pods-gutenbergTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-gutenbergTests.debug.xcconfig"; path = "Target Support Files/Pods-gutenbergTests/Pods-gutenbergTests.debug.xcconfig"; sourceTree = ""; }; 083D3CAD9B26701AE0659EEB /* Pods-gutenberg.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-gutenberg.release.xcconfig"; path = "Target Support Files/Pods-gutenberg/Pods-gutenberg.release.xcconfig"; sourceTree = ""; }; 0EB766FE2F6D446A80AC6E6A /* libRNSVG.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSVG.a; sourceTree = ""; }; - 13B07F961A680F5B00A75B9A /* GutenbergDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GutenbergDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07F961A680F5B00A75B9A /* gutenberg.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = gutenberg.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = gutenberg/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = gutenberg/Info.plist; sourceTree = ""; }; @@ -218,7 +218,7 @@ 83CBBA001A601CBA00E9B192 /* Products */ = { isa = PBXGroup; children = ( - 13B07F961A680F5B00A75B9A /* GutenbergDemo.app */, + 13B07F961A680F5B00A75B9A /* gutenberg.app */, 00E356EE1AD99517003FC87E /* gutenbergTests.xctest */, 2D02E47B1E0B4A5D006451C7 /* gutenberg-tvOS.app */, 2D02E4901E0B4A5D006451C7 /* gutenberg-tvOSTests.xctest */, @@ -312,7 +312,7 @@ ); name = gutenberg; productName = "Hello World"; - productReference = 13B07F961A680F5B00A75B9A /* GutenbergDemo.app */; + productReference = 13B07F961A680F5B00A75B9A /* gutenberg.app */; productType = "com.apple.product-type.application"; }; 2D02E47A1E0B4A5D006451C7 /* gutenberg-tvOS */ = { @@ -823,7 +823,7 @@ "-lc++", ); PRODUCT_BUNDLE_IDENTIFIER = org.wordpress.gutenberg.development; - PRODUCT_NAME = GutenbergDemo; + PRODUCT_NAME = gutenberg; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "gutenberg-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -857,7 +857,7 @@ "-lc++", ); PRODUCT_BUNDLE_IDENTIFIER = org.wordpress.gutenberg.development; - PRODUCT_NAME = GutenbergDemo; + PRODUCT_NAME = gutenberg; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "gutenberg-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/packages/react-native-editor/ios/gutenberg.xcodeproj/xcshareddata/xcschemes/gutenberg.xcscheme b/packages/react-native-editor/ios/gutenberg.xcodeproj/xcshareddata/xcschemes/gutenberg.xcscheme index 8c6a40ffb9c92..47d5a4d08053f 100644 --- a/packages/react-native-editor/ios/gutenberg.xcodeproj/xcshareddata/xcschemes/gutenberg.xcscheme +++ b/packages/react-native-editor/ios/gutenberg.xcodeproj/xcshareddata/xcschemes/gutenberg.xcscheme @@ -29,7 +29,7 @@ @@ -59,7 +59,7 @@ @@ -92,7 +92,7 @@ @@ -109,7 +109,7 @@ diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 2dfa9787b3c51..72568ed60b7e4 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -114,7 +114,7 @@ "test:e2e:bundle:android": "mkdir -p android/app/src/main/assets && npm run rn-bundle -- --reset-cache --platform android --dev false --minify false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res", "test:e2e:build-app:android": "npm run test:e2e:bundle:android && cd android && ./gradlew clean && ./gradlew assembleDebug", "test:e2e:install-app:android": "cd android && ./gradlew installDebug", - "test:e2e:bundle:ios": "mkdir -p ios/build/gutenberg/Build/Products/Release-iphonesimulator/GutenbergDemo.app && npm run rn-bundle -- --reset-cache --platform=ios --dev=false --minify false --entry-file=index.js --bundle-output=./ios/build/gutenberg/Build/Products/Release-iphonesimulator/GutenbergDemo.app/main.jsbundle --assets-dest=./ios/build/gutenberg/Build/Products/Release-iphonesimulator/GutenbergDemo.app", + "test:e2e:bundle:ios": "mkdir -p ios/build/gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app && npm run rn-bundle -- --reset-cache --platform=ios --dev=false --minify false --entry-file=index.js --bundle-output=./ios/build/gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app/main.jsbundle --assets-dest=./ios/build/gutenberg/Build/Products/Release-iphonesimulator/gutenberg.app", "test:e2e:build-app:ios": "npm run ios -- --configuration Release --no-packager --simulator 'iPhone 11 (13.4)'", "build:gutenberg": "cd gutenberg && npm ci && npm run build", "clean": "npm run clean:build-artifacts; npm run clean:aztec; npm run clean:haste; npm run clean:jest; npm run clean:metro; npm run clean:react; npm run clean:watchman", From bf0b8ab18f061bf1360171277769fd8fc7f6d0fc Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 25 Sep 2020 17:53:53 +1000 Subject: [PATCH 07/79] Navigation: Handle block menu items (#24846) * Navigation: Handle block menu items Handle menu items with type = 'block' when creating a Navigation block from an existing menu. * Navigation: Update E2E test snapshots --- .../src/navigation/placeholder.js | 62 +++++++++++++------ .../__snapshots__/navigation.test.js.snap | 32 +++++----- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/packages/block-library/src/navigation/placeholder.js b/packages/block-library/src/navigation/placeholder.js index 0e50078babbb8..548ffd3a21434 100644 --- a/packages/block-library/src/navigation/placeholder.js +++ b/packages/block-library/src/navigation/placeholder.js @@ -1,12 +1,13 @@ /** * External dependencies */ +import { some } from 'lodash'; import classnames from 'classnames'; /** * WordPress dependencies */ -import { createBlock } from '@wordpress/blocks'; +import { createBlock, parse } from '@wordpress/blocks'; import { Button, CustomSelectControl, @@ -76,26 +77,51 @@ function getSelectedMenu( selectedCreateOption ) { /** * A recursive function that maps menu item nodes to blocks. * - * @param {Object[]} nodes An array of menu items. - * + * @param {Object[]} menuItems An array of menu items. * @return {WPBlock[]} An array of blocks. */ -function mapMenuItemsToBlocks( nodes ) { - return nodes.map( ( { title, type, link: url, id, children } ) => { - const innerBlocks = - children && children.length ? mapMenuItemsToBlocks( children ) : []; +function mapMenuItemsToBlocks( menuItems ) { + return menuItems.map( ( menuItem ) => { + if ( menuItem.type === 'block' ) { + const [ block ] = parse( menuItem.content.raw ); - return createBlock( - 'core/navigation-link', - { - type, - id, - url, - label: ! title.rendered ? __( '(no title)' ) : title.rendered, - opensInNewTab: false, - }, - innerBlocks - ); + if ( ! block ) { + return createBlock( 'core/freeform', { + content: menuItem.content, + } ); + } + + return block; + } + + const attributes = { + label: ! menuItem.title.rendered + ? __( '(no title)' ) + : menuItem.title.rendered, + opensInNewTab: menuItem.target === '_blank', + }; + + if ( menuItem.url ) { + attributes.url = menuItem.url; + } + + if ( menuItem.description ) { + attributes.description = menuItem.description; + } + + if ( menuItem.xfn?.length && some( menuItem.xfn ) ) { + attributes.rel = menuItem.xfn.join( ' ' ); + } + + if ( menuItem.classes?.length && some( menuItem.classes ) ) { + attributes.className = menuItem.classes.join( ' ' ); + } + + const innerBlocks = menuItem.children?.length + ? mapMenuItemsToBlocks( menuItem.children ) + : []; + + return createBlock( 'core/navigation-link', attributes, innerBlocks ); } ); } diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap index eb7a6c163f2f3..9aa697a87c723 100644 --- a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap @@ -2,36 +2,36 @@ exports[`Navigation Creating from existing Menus allows a navigation block to be created from existing menus 1`] = ` " - + - - + + - - - - - + + + + + - + - + - - - - + + + + - - + + " `; From 6ade0d28a45e2bf6089a2ed33e2dcd354bb10003 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Fri, 25 Sep 2020 17:36:22 +0800 Subject: [PATCH 08/79] Remove block list appender and use the default paragraph appender instead (#25635) --- packages/block-library/src/widget-area/edit/inner-blocks.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-library/src/widget-area/edit/inner-blocks.js b/packages/block-library/src/widget-area/edit/inner-blocks.js index 15ca45640a4c1..cf23812646eac 100644 --- a/packages/block-library/src/widget-area/edit/inner-blocks.js +++ b/packages/block-library/src/widget-area/edit/inner-blocks.js @@ -15,7 +15,6 @@ export default function WidgetAreaInnerBlocks() { onInput={ onInput } onChange={ onChange } templateLock={ false } - renderAppender={ InnerBlocks.ButtonBlockAppender } /> ); } From 165d63beb1c78c8f59ec45e46621ba5ce0e1e9dc Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Fri, 25 Sep 2020 12:36:45 +0200 Subject: [PATCH 09/79] Mobile - Image block - Update image url after uploading if the block is not selected (#25529) --- packages/block-library/src/image/edit.native.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 2b547af3c146a..e48e0bb401544 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -38,7 +38,7 @@ import { BlockStyles, } from '@wordpress/block-editor'; import { __, sprintf } from '@wordpress/i18n'; -import { getProtocol } from '@wordpress/url'; +import { getProtocol, hasQueryArg } from '@wordpress/url'; import { doAction, hasAction } from '@wordpress/hooks'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; @@ -541,9 +541,16 @@ export default compose( [ isSelected, } = props; const { imageSizes } = getSettings(); + const isNotFileUrl = id && getProtocol( url ) !== 'file:'; const shouldGetMedia = - id && isSelected && getProtocol( url ) !== 'file:'; + ( isSelected && isNotFileUrl ) || + // Edge case to update the image after uploading if the block gets unselected + // Check if it's the original image and not the resized one with queryparams + ( ! isSelected && + isNotFileUrl && + url && + ! hasQueryArg( url, 'w' ) ); return { image: shouldGetMedia ? getMedia( id ) : null, imageSizes, From 0be6705e23d02407058e3ca905100d4dbb548073 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Fri, 25 Sep 2020 20:24:51 +0800 Subject: [PATCH 10/79] [Widgets] Add titles to Legacy Widgets (#25638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change the default presentation of widgets * Refactor to function component and fix iframe height * Update style * Correctly get the iframe height after the component is not hidden * Show widget titles by default * Update packages/edit-widgets/src/blocks/legacy-widget/editor.scss Co-authored-by: Kai Hao * Address feedback * Update CSS rules * Hide editor body in an accessible way Co-authored-by: Adam Zieliński --- .../src/blocks/legacy-widget/edit/handler.js | 31 +- .../src/blocks/legacy-widget/edit/index.js | 312 ++++++++---------- .../legacy-widget/edit/widget-preview.js | 39 ++- .../src/blocks/legacy-widget/editor.scss | 11 +- 4 files changed, 199 insertions(+), 194 deletions(-) diff --git a/packages/edit-widgets/src/blocks/legacy-widget/edit/handler.js b/packages/edit-widgets/src/blocks/legacy-widget/edit/handler.js index a2703783cf006..e4fb1843ecca0 100644 --- a/packages/edit-widgets/src/blocks/legacy-widget/edit/handler.js +++ b/packages/edit-widgets/src/blocks/legacy-widget/edit/handler.js @@ -64,7 +64,7 @@ class LegacyWidgetEditHandler extends Component { number, idBase, instance, - isSelected, + isVisible, widgetName, } = this.props; const { form } = this.state; @@ -75,17 +75,26 @@ class LegacyWidgetEditHandler extends Component { const widgetTitle = get( instance, [ 'title' ] ); let title = null; - if ( isSelected ) { - if ( widgetTitle && widgetName ) { - title = `${ widgetName }: ${ widgetTitle }`; - } else if ( ! widgetTitle && widgetName ) { - title = widgetName; - } else if ( widgetTitle && ! widgetName ) { - title = widgetTitle; - } + if ( widgetTitle && widgetName ) { + title = `${ widgetName }: ${ widgetTitle }`; + } else if ( ! widgetTitle && widgetName ) { + title = widgetName; + } else if ( widgetTitle && ! widgetName ) { + title = widgetTitle; } + + const componentProps = isVisible + ? {} + : { + style: { display: 'none' }, + hidden: true, + 'aria-hidden': 'true', + }; return ( - <> +
{ title && (
{ title } @@ -114,7 +123,7 @@ class LegacyWidgetEditHandler extends Component { form={ form } />
- +
); } diff --git a/packages/edit-widgets/src/blocks/legacy-widget/edit/index.js b/packages/edit-widgets/src/blocks/legacy-widget/edit/index.js index 7f690bfec7a98..649d059e299b7 100644 --- a/packages/edit-widgets/src/blocks/legacy-widget/edit/index.js +++ b/packages/edit-widgets/src/blocks/legacy-widget/edit/index.js @@ -6,7 +6,7 @@ import { get, omit } from 'lodash'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { useState } from '@wordpress/element'; import { Button, PanelBody, ToolbarGroup } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withSelect } from '@wordpress/data'; @@ -20,190 +20,168 @@ import LegacyWidgetEditHandler from './handler'; import LegacyWidgetPlaceholder from './placeholder'; import WidgetPreview from './widget-preview'; -class LegacyWidgetEdit extends Component { - constructor() { - super( ...arguments ); - this.state = { - hasEditForm: true, - isPreview: false, - }; - this.switchToEdit = this.switchToEdit.bind( this ); - this.switchToPreview = this.switchToPreview.bind( this ); - this.changeWidget = this.changeWidget.bind( this ); - } +function LegacyWidgetEdit( { + attributes, + availableLegacyWidgets, + hasPermissionsToManageWidgets, + prerenderedEditForm, + setAttributes, + widgetId, + WPWidget, + widgetAreaId, +} ) { + const [ hasEditForm, setHasEditForm ] = useState( true ); + const [ isPreview, setIsPreview ] = useState( false ); + const shouldHidePreview = ! isPreview && hasEditForm; - render() { - const { - attributes, - availableLegacyWidgets, - hasPermissionsToManageWidgets, - isSelected, - prerenderedEditForm, - setAttributes, - widgetId, - WPWidget, - } = this.props; - const { isPreview, hasEditForm } = this.state; - if ( ! WPWidget ) { - return ( - { - const { - isReferenceWidget, - id_base: idBase, - } = availableLegacyWidgets[ newWidget ]; + function changeWidget() { + switchToEdit(); - if ( isReferenceWidget ) { - setAttributes( { - instance: {}, - idBase, - referenceWidgetName: newWidget, - widgetClass: undefined, - } ); - } else { - setAttributes( { - instance: {}, - idBase, - referenceWidgetName: undefined, - widgetClass: newWidget, - } ); - } - } } - /> - ); - } - - const inspectorControls = WPWidget ? ( - - - { WPWidget.description } - - - ) : null; - if ( ! hasPermissionsToManageWidgets ) { - return ( - <> - { inspectorControls } - { this.renderWidgetPreview() } - - ); - } - - return ( - <> - - - { WPWidget && ! WPWidget.isHidden && ( - - - - ) } - - - { inspectorControls } - { hasEditForm && ( - { - // Function-based widgets don't come with an object of settings, only - // with a pre-rendered HTML form. Extracting settings from that HTML - // before this stage is not trivial (think embedded add_setting( - 'gutenberg_widget_blocks', - array( - 'default' => '{}', - 'type' => 'gutenberg_widget_blocks', - 'capability' => 'edit_theme_options', - 'transport' => 'postMessage', - 'sanitize_callback' => 'gutenberg_customize_sanitize', - ) - ); - - if ( gutenberg_use_widgets_block_editor() ) { - $wp_customize->add_section( - 'gutenberg_widget_blocks', - array( 'title' => __( 'Widget Blocks', 'gutenberg' ) ) - ); - $wp_customize->add_control( - new WP_Customize_Widget_Blocks_Control( - $wp_customize, - 'gutenberg_widget_blocks', - array( - 'section' => 'gutenberg_widget_blocks', - 'settings' => 'gutenberg_widget_blocks', - ) - ) - ); - } -} -add_action( 'customize_register', 'gutenberg_customize_register' ); - -/** - * Removes the core 'Widgets' panel from the Customizer if block based widgets are enabled. - * - * @param array $components Core Customizer components list. - * @return array (Maybe) modified components list. - */ -function gutenberg_remove_widgets_panel( $components ) { - if ( ! gutenberg_use_widgets_block_editor() ) { - return $components; - } - - $i = array_search( 'widgets', $components, true ); - if ( false !== $i ) { - unset( $components[ $i ] ); - } - return $components; -} -add_filter( 'customize_loaded_components', 'gutenberg_remove_widgets_panel' ); - -/** - * Filters the Customizer widget settings arguments. - * This is needed because the Customizer registers settings for the raw registered widgets, without going through the `sidebars_widgets` filter. - * The `WP_Customize_Widgets` class expects sidebars to have an array of widgets registered, not a post ID. - * This results in the value passed to `sanitize_js_callback` being `null` and throwing an error. - * - * TODO: Figure out why core is not running the `sidebars_widgets` filter for the relevant part of the code. - * Then, either fix it or change this filter to parse the post IDs and then pass them to the original `sanitize_js_callback`. - * - * @param array $args Array of Customizer setting arguments. - * @param string $id Widget setting ID. - * @return array Maybe modified array of Customizer setting arguments. - */ -function filter_widget_customizer_setting_args( $args, $id = null ) { - // Posts won't have a settings ID like widgets. We can use that to remove the sanitization callback. - if ( ! isset( $id ) ) { - unset( $args['sanitize_js_callback'] ); - } - - return $args; -} -add_filter( 'widget_customizer_setting_args', 'filter_widget_customizer_setting_args' ); diff --git a/lib/load.php b/lib/load.php index 540539f56ba59..435941c323d0d 100644 --- a/lib/load.php +++ b/lib/load.php @@ -113,7 +113,6 @@ function gutenberg_is_experiment_enabled( $name ) { require dirname( __FILE__ ) . '/navigation.php'; require dirname( __FILE__ ) . '/navigation-page.php'; require dirname( __FILE__ ) . '/experiments-page.php'; -require dirname( __FILE__ ) . '/customizer.php'; require dirname( __FILE__ ) . '/global-styles.php'; require dirname( __FILE__ ) . '/block-supports/index.php'; require dirname( __FILE__ ) . '/block-supports/align.php'; diff --git a/lib/widgets-page.php b/lib/widgets-page.php index 4a12f51e550e3..f69451a22a6c8 100644 --- a/lib/widgets-page.php +++ b/lib/widgets-page.php @@ -9,20 +9,12 @@ * The main entry point for the Gutenberg widgets page. * * @since 5.2.0 - * - * @param string $page The page name the function is being called for, `'gutenberg_customizer'` for the Customizer. */ -function the_gutenberg_widgets( $page = 'appearance_page_gutenberg-widgets' ) { +function the_gutenberg_widgets() { ?>
( { - ...settings, - hasFixedToolbar: true, - } ), - [ settings ] - ); - return ( - -
-
- - - - - -
-
- ); -} - -export default navigateRegions( CustomizerEditWidgetsInitializer ); diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js deleted file mode 100644 index 4752d0305c0ba..0000000000000 --- a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js +++ /dev/null @@ -1,187 +0,0 @@ -/** - * External dependencies - */ -import { throttle } from 'lodash'; - -/** - * WordPress dependencies - */ -import { parse, serialize } from '@wordpress/blocks'; - -/* -Widget area edits made in the Customizer are synced to Customizer -changesets as an object, encoded as a JSON string, where the keys -are widget area IDs and the values are serialized block content. -This file takes care of that syncing using the 2-way data binding -supported by `WP_Customize_Control`s. The process is as follows: - -- On load, the client checks if the current changeset has -widget areas that it can parse and use to hydrate the store. -It will load all widget areas for the current theme, but if -the changeset has content for a given area, it will replace -its actual published content with the changeset's. - -- On edit, the client updates the 2-way bound input with a new object that maps -widget area IDs and the values are serialized block content, encoded -as a JSON string. - -- On publish, a PHP action will parse the JSON string in the -changeset and update all the widget areas in it, to store the -new content. -*/ - -const waitForSelectValue = ( listener, value, changeTrigger ) => { - return new Promise( ( resolve ) => { - changeTrigger(); - if ( listener() === value ) { - resolve(); - return; - } - const unsubscribe = window.wp.data.subscribe( () => { - if ( listener() === value ) { - unsubscribe(); - resolve(); - } - } ); - } ); -}; - -// Get widget areas from the store in an `id => blocks` mapping. -const getWidgetAreasObject = () => { - const { getEntityRecords, getEditedEntityRecord } = window.wp.data.select( - 'core' - ); - - return getEntityRecords( 'root', 'widgetArea' ).reduce( - ( widgetAreasObject, { id } ) => { - widgetAreasObject[ id ] = getEditedEntityRecord( - 'root', - 'widgetArea', - id - ).blocks; - return widgetAreasObject; - }, - {} - ); -}; - -// Serialize the provided blocks and render them in the widget area with the provided ID. -const previewBlocksInWidgetArea = throttle( ( id, blocks ) => { - const customizePreviewIframe = document.querySelector( - '#customize-preview > iframe' - ); - if ( - ! customizePreviewIframe || - ! customizePreviewIframe.contentDocument - ) { - return; - } - - const widgetArea = customizePreviewIframe.contentDocument.querySelector( - `[data-customize-partial-placement-context*='"sidebar_id":"${ id }"']` - ); - if ( widgetArea ) { - widgetArea.innerHTML = serialize( blocks ); - widgetArea.parentElement.innerHTML = widgetArea.outerHTML; - } -}, 1000 ); - -// Update the hidden input that has 2-way data binding with Customizer settings. -const updateSettingInputValue = throttle( ( nextWidgetAreas ) => { - const settingInput = document.getElementById( - '_customize-input-gutenberg_widget_blocks' - ); - if ( settingInput ) { - settingInput.value = JSON.stringify( - Object.keys( nextWidgetAreas ).reduce( ( value, id ) => { - value[ id ] = serialize( nextWidgetAreas[ id ] ); - return value; - }, {} ) - ); - settingInput.dispatchEvent( new window.Event( 'change' ) ); - } -}, 1000 ); - -// Check that all the necessary globals are present. -if ( window.wp && window.wp.customize && window.wp.data ) { - let ran = false; - // Wait for the Customizer to finish bootstrapping. - window.wp.customize.bind( 'ready', () => - window.wp.customize.previewer.bind( 'ready', () => { - // The Customizer will call this on every preview refresh, - // but we only want to run it once to avoid running another - // store setup that would set changeset edits and save - // widget blocks unintentionally. - if ( ran ) { - return; - } - ran = true; - - // Try to parse a previous changeset from the hidden input. - let widgetAreas; - try { - widgetAreas = JSON.parse( - document.getElementById( - '_customize-input-gutenberg_widget_blocks' - ).value - ); - widgetAreas = Object.keys( widgetAreas ).reduce( - ( value, id ) => { - value[ id ] = parse( widgetAreas[ id ] ); - return value; - }, - {} - ); - } catch ( err ) { - widgetAreas = {}; - } - - // Wait for setup to finish before overwriting sidebars with changeset data, - // if any, and subscribe to registry changes after that so that we can preview - // changes and update the hidden input's value when any of the widget areas change. - waitForSelectValue( - () => - window.wp.data - .select( 'core' ) - .hasFinishedResolution( 'getEntityRecords', [ - 'root', - 'widgetArea', - ] ), - true, - () => - window.wp.data - .select( 'core' ) - .getEntityRecords( 'root', 'widgetArea' ) - ).then( () => { - Object.keys( widgetAreas ).forEach( ( id ) => { - window.wp.data - .dispatch( 'core' ) - .editEntityRecord( 'root', 'widgetArea', id, { - content: serialize( widgetAreas[ id ] ), - blocks: widgetAreas[ id ], - } ); - } ); - widgetAreas = getWidgetAreasObject(); - window.wp.data.subscribe( () => { - const nextWidgetAreas = getWidgetAreasObject(); - - let didUpdate = false; - for ( const id of Object.keys( nextWidgetAreas ) ) { - if ( widgetAreas[ id ] !== nextWidgetAreas[ id ] ) { - previewBlocksInWidgetArea( - id, - nextWidgetAreas[ id ] - ); - didUpdate = true; - } - } - - if ( didUpdate ) { - updateSettingInputValue( nextWidgetAreas ); - } - widgetAreas = nextWidgetAreas; - } ); - } ); - } ) - ); -} diff --git a/packages/edit-widgets/src/components/header/index.js b/packages/edit-widgets/src/components/header/index.js index 49fe8e3458124..51c6962e258b2 100644 --- a/packages/edit-widgets/src/components/header/index.js +++ b/packages/edit-widgets/src/components/header/index.js @@ -23,7 +23,7 @@ import RedoButton from './undo-redo/redo'; const inserterToggleProps = { isPrimary: true }; -function Header( { isCustomizer } ) { +function Header() { const isLargeViewport = useViewportMatch( 'medium' ); const rootClientId = useLastSelectedRootId(); const isLastSelectedWidgetAreaOpen = useSelect( @@ -68,23 +68,15 @@ function Header( { isCustomizer } ) { - { ! isCustomizer && ( -

- { __( 'Block Areas' ) } -

- ) } +

+ { __( 'Block Areas' ) } +

- { ! isCustomizer && } - + +
- { ( ! isLargeViewport || isCustomizer ) && ( + { ! isLargeViewport && (
diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index 7c59725da5c95..c3d5b063c86af 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -19,7 +19,6 @@ import './store'; import './hooks'; import { create as createLegacyWidget } from './blocks/legacy-widget'; import EditWidgetsInitializer from './components/edit-widgets-initializer'; -import CustomizerEditWidgetsInitializer from './components/customizer-edit-widgets-initializer'; /** * Initializes the block editor in the widgets screen. @@ -39,24 +38,6 @@ export function initialize( id, settings ) { ); } -/** - * Initializes the block editor in the widgets Customizer section. - * - * @param {string} id ID of the root element to render the section in. - * @param {Object} settings Block editor settings. - */ -export function customizerInitialize( id, settings ) { - registerCoreBlocks(); - if ( process.env.GUTENBERG_PHASE === 2 ) { - __experimentalRegisterExperimentalCoreBlocks( settings ); - registerBlock( createLegacyWidget( settings ) ); - } - render( - , - document.getElementById( id ) - ); -} - /** * Function to register an individual block. * diff --git a/packages/edit-widgets/src/style.scss b/packages/edit-widgets/src/style.scss index 102c7e203eff5..0b1af92ed323a 100644 --- a/packages/edit-widgets/src/style.scss +++ b/packages/edit-widgets/src/style.scss @@ -1,7 +1,6 @@ @import "../../interface/src/style.scss"; @import "./blocks/legacy-widget/editor.scss"; -@import "./components/customizer-edit-widgets-initializer/style.scss"; @import "./components/header/style.scss"; @import "./components/sidebar/style.scss"; @import "./components/notices/style.scss"; @@ -44,12 +43,6 @@ body.appearance_page_gutenberg-widgets { height: 100%; } - &.is-in-customizer { - min-height: initial; - position: initial; - } - - .interface-interface-skeleton__content { background-color: #f1f1f1; } From 5fb09ec3ee6f56195260b6eb619d127801a4cf71 Mon Sep 17 00:00:00 2001 From: etoledom Date: Mon, 28 Sep 2020 11:40:29 +0200 Subject: [PATCH 24/79] [RNMobile] Overwrite Missing block Alert Strings (#25238) * Testing UIStringContext * Testing UIStringContext * Unsupported Block Editor: Missing block alert text can be modified from the client * Update Missing block action button text * Fix Missing block mobile unit test * Add Android bridge methods for sending switch condition and receiving button pressed action * Add missing method implementation * Move updateSettings call from return() to componentDidMount() * Remove Context implementation from ui-strings * iOS: Making open `onGutenbergReady` to be overriden * Rename `isUnsupportedBlockSwitchable` to `canEnableUnsupportedBlockEditor` * Update bridge with capability name change to canEnableUnsupportedBlockEditor * Fix lint errors * Revert testing changes from GutenbergViewController Demo Co-authored-by: Marko Savic --- .../block-library/src/missing/edit.native.js | 62 +++++++++++++------ .../src/missing/test/edit.native.js | 6 +- packages/components/src/index.native.js | 1 + .../src/mobile/ui-strings/index.native.js | 12 ++++ .../src/components/provider/index.native.js | 6 +- .../GutenbergBridgeJS2Parent.java | 4 +- .../RNReactNativeGutenbergBridgeModule.java | 5 ++ .../mobile/WPAndroidGlue/GutenbergProps.kt | 3 + .../WPAndroidGlue/WPAndroidGlueCode.java | 12 ++++ packages/react-native-bridge/index.js | 13 ++++ .../ios/GutenbergBridgeDelegate.swift | 4 ++ ...utenbergWebSingleBlockViewController.swift | 24 ++++--- .../ios/RNReactNativeGutenbergBridge.m | 1 + .../ios/RNReactNativeGutenbergBridge.swift | 16 +++++ .../java/com/gutenberg/MainApplication.java | 5 ++ 15 files changed, 138 insertions(+), 36 deletions(-) create mode 100644 packages/components/src/mobile/ui-strings/index.native.js diff --git a/packages/block-library/src/missing/edit.native.js b/packages/block-library/src/missing/edit.native.js index b0f97d48a41c5..b6fa3a4415e4c 100644 --- a/packages/block-library/src/missing/edit.native.js +++ b/packages/block-library/src/missing/edit.native.js @@ -1,13 +1,17 @@ /** * External dependencies */ -import { Platform, View, Text, TouchableWithoutFeedback } from 'react-native'; +import { View, Text, TouchableWithoutFeedback } from 'react-native'; /** * WordPress dependencies */ -import { requestUnsupportedBlockFallback } from '@wordpress/react-native-bridge'; -import { BottomSheet, Icon } from '@wordpress/components'; +import { + requestUnsupportedBlockFallback, + sendActionButtonPressedAction, + actionButtons, +} from '@wordpress/react-native-bridge'; +import { BottomSheet, Icon, withUIStrings } from '@wordpress/components'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { coreBlocks } from '@wordpress/block-library'; import { normalizeIconObject } from '@wordpress/blocks'; @@ -60,8 +64,16 @@ export class UnsupportedBlockEdit extends Component { } requestFallback() { - this.toggleSheet(); - this.setState( { sendFallbackMessage: true } ); + if ( + this.props.canEnableUnsupportedBlockEditor && + this.props.isUnsupportedBlockEditorSupported === false + ) { + this.toggleSheet(); + this.setState( { sendButtonPressMessage: true } ); + } else { + this.toggleSheet(); + this.setState( { sendFallbackMessage: true } ); + } } renderSheet( blockTitle, blockName ) { @@ -70,6 +82,7 @@ export class UnsupportedBlockEdit extends Component { attributes, clientId, isUnsupportedBlockEditorSupported, + canEnableUnsupportedBlockEditor, } = this.props; const infoTextStyle = getStylesFromColorScheme( styles.infoText, @@ -88,12 +101,8 @@ export class UnsupportedBlockEdit extends Component { styles.infoSheetIconDark ); - const titleFormat = - Platform.OS === 'android' - ? // translators: %s: Name of the block - __( "'%s' isn't yet supported on WordPress for Android" ) - : // translators: %s: Name of the block - __( "'%s' isn't yet supported on WordPress for iOS" ); + /* translators: Missing block alert title. %s: The localized block name */ + const titleFormat = __( "'%s' is not fully-supported" ); const infoTitle = sprintf( titleFormat, blockTitle ); const actionButtonStyle = getStylesFromColorScheme( @@ -124,6 +133,13 @@ export class UnsupportedBlockEdit extends Component { ); }, 100 ); this.setState( { sendFallbackMessage: false } ); + } else if ( this.state.sendButtonPressMessage ) { + this.timeout = setTimeout( () => { + sendActionButtonPressedAction( + actionButtons.missingBlockAlertActionButton + ); + }, 100 ); + this.setState( { sendButtonPressMessage: false } ); } } } > @@ -137,19 +153,21 @@ export class UnsupportedBlockEdit extends Component { { infoTitle } - { isUnsupportedBlockEditorSupported - ? __( - "We are working hard to add more blocks with each release. In the meantime, you can also edit this block using your device's web browser." - ) - : __( - 'We are working hard to add more blocks with each release. In the meantime, you can also edit this post on the web.' - ) } + { this.props.uiStrings[ 'missing-block-detail' ] ?? + __( + 'We are working hard to add more blocks with each release.' + ) } - { isUnsupportedBlockEditorSupported && ( + { ( isUnsupportedBlockEditorSupported || + canEnableUnsupportedBlockEditor ) && ( <> { const testInstance = component.root; const bottomSheet = testInstance.findByType( BottomSheet ); const children = bottomSheet.props.children[ 0 ].props.children; - const expectedOSString = Platform.OS === 'ios' ? 'iOS' : 'Android'; expect( children[ 1 ].props.children ).toBe( "'" + defaultAttributes.originalName + - "' isn't yet supported on WordPress for " + - expectedOSString + "' is not fully-supported" ); } ); } ); diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index fa2cbb9e8c63d..26b233cc1c706 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -90,3 +90,4 @@ export { useGlobalStyles, withGlobalStyles, } from './mobile/global-styles-context'; +export * from './mobile/ui-strings'; diff --git a/packages/components/src/mobile/ui-strings/index.native.js b/packages/components/src/mobile/ui-strings/index.native.js new file mode 100644 index 0000000000000..8452c60b177f0 --- /dev/null +++ b/packages/components/src/mobile/ui-strings/index.native.js @@ -0,0 +1,12 @@ +const stringsToAdd = []; + +export const addStrings = ( newStrings ) => stringsToAdd.unshift( newStrings ); + +const getStrings = () => + stringsToAdd.reduce( ( previous, current ) => { + return { ...previous, ...current() }; + }, {} ); + +export const withUIStrings = ( WrappedComponent ) => ( props ) => ( + +); diff --git a/packages/editor/src/components/provider/index.native.js b/packages/editor/src/components/provider/index.native.js index 16ae51def2970..d6e7b93a44fd8 100644 --- a/packages/editor/src/components/provider/index.native.js +++ b/packages/editor/src/components/provider/index.native.js @@ -72,6 +72,10 @@ class NativeEditorProvider extends Component { } componentDidMount() { + const { capabilities } = this.props; + + this.props.updateSettings( capabilities ); + this.subscriptionParentGetHtml = subscribeParentGetHtml( () => { this.serializeToNativeAction(); } ); @@ -247,11 +251,9 @@ class NativeEditorProvider extends Component { const { children, post, // eslint-disable-line no-unused-vars - capabilities, ...props } = this.props; - this.props.updateSettings( capabilities ); return ( { children } diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java index 3787477acde2b..cc5ba27356217 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java @@ -148,8 +148,10 @@ void gutenbergDidRequestUnsupportedBlockFallback(ReplaceUnsupportedBlockCallback String blockName, String blockTitle); + void gutenbergDidSendButtonPressedAction(String buttonType); + void onAddMention(Consumer onSuccess); - + void setStarterPageTemplatesTooltipShown(boolean tooltipShown); void requestStarterPageTemplatesTooltipShown(StarterPageTemplatesTooltipShownCallback starterPageTemplatesTooltipShownCallback); diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java index f2177265e69fb..f7e18e9e1bb12 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java @@ -263,6 +263,11 @@ public void requestUnsupportedBlockFallback(String content, String blockId, Stri replaceBlock(savedContent, savedBlockId), content, blockId, blockName, blockTitle); } + @ReactMethod + public void actionButtonPressed(String buttonType) { + mGutenbergBridgeJS2Parent.gutenbergDidSendButtonPressedAction(buttonType); + } + private void replaceBlock(String content, String blockId) { WritableMap writableMap = new WritableNativeMap(); writableMap.putString(MAP_KEY_REPLACE_BLOCK_HTML, content); diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt index ed211a3588fa0..c7d4172319696 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt @@ -5,6 +5,7 @@ import android.os.Bundle data class GutenbergProps @JvmOverloads constructor( val enableMentions: Boolean, val enableUnsupportedBlockEditor: Boolean, + val canEnableUnsupportedBlockEditor: Boolean, val localeSlug: String, val postType: String, val editorTheme: Bundle?, @@ -33,6 +34,7 @@ data class GutenbergProps @JvmOverloads constructor( fun getUpdatedCapabilitiesProps() = Bundle().apply { putBoolean(PROP_CAPABILITIES_MENTIONS, enableMentions) putBoolean(PROP_CAPABILITIES_UNSUPPORTED_BLOCK_EDITOR, enableUnsupportedBlockEditor) + putBoolean(PROP_CAPABILITIES_CAN_ENABLE_UNSUPPORTED_BLOCK_EDITOR, canEnableUnsupportedBlockEditor) putBoolean(PROP_CAPABILITIES_MODAL_LAYOUT_PICKER, isModalLayoutPickerEnabled) } @@ -56,6 +58,7 @@ data class GutenbergProps @JvmOverloads constructor( const val PROP_CAPABILITIES = "capabilities" const val PROP_CAPABILITIES_MENTIONS = "mentions" const val PROP_CAPABILITIES_UNSUPPORTED_BLOCK_EDITOR = "unsupportedBlockEditor" + const val PROP_CAPABILITIES_CAN_ENABLE_UNSUPPORTED_BLOCK_EDITOR = "canEnableUnsupportedBlockEditor" const val PROP_CAPABILITIES_MODAL_LAYOUT_PICKER = "modalLayoutPicker" } } diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java index b8463d07557cf..4fb9793423049 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java @@ -89,6 +89,7 @@ public class WPAndroidGlueCode { private OnMediaEditorListener mOnMediaEditorListener; private OnLogGutenbergUserEventListener mOnLogGutenbergUserEventListener; private OnGutenbergDidRequestUnsupportedBlockFallbackListener mOnGutenbergDidRequestUnsupportedBlockFallbackListener; + private OnGutenbergDidSendButtonPressedActionListener mOnGutenbergDidSendButtonPressedActionListener; private ReplaceUnsupportedBlockCallback mReplaceUnsupportedBlockCallback; private OnStarterPageTemplatesTooltipShownEventListener mOnStarterPageTemplatesTooltipShownListener; private boolean mIsEditorMounted; @@ -177,6 +178,10 @@ public interface OnGutenbergDidRequestUnsupportedBlockFallbackListener { void gutenbergDidRequestUnsupportedBlockFallback(UnsupportedBlock unsupportedBlock); } + public interface OnGutenbergDidSendButtonPressedActionListener { + void gutenbergDidSendButtonPressedAction(String buttonType); + } + public interface OnStarterPageTemplatesTooltipShownEventListener { void onSetStarterPageTemplatesTooltipShown(boolean tooltipShown); boolean onRequestStarterPageTemplatesTooltipShown(); @@ -373,6 +378,11 @@ public void gutenbergDidRequestUnsupportedBlockFallback(ReplaceUnsupportedBlockC gutenbergDidRequestUnsupportedBlockFallback(new UnsupportedBlock(blockId, blockName, blockTitle, content)); } + @Override + public void gutenbergDidSendButtonPressedAction(String buttonType) { + mOnGutenbergDidSendButtonPressedActionListener.gutenbergDidSendButtonPressedAction(buttonType); + } + @Override public void onAddMention(Consumer onSuccess) { mAddMentionUtil.getMention(onSuccess); @@ -460,6 +470,7 @@ public void attachToContainer(ViewGroup viewGroup, OnMediaEditorListener onMediaEditorListener, OnLogGutenbergUserEventListener onLogGutenbergUserEventListener, OnGutenbergDidRequestUnsupportedBlockFallbackListener onGutenbergDidRequestUnsupportedBlockFallbackListener, + OnGutenbergDidSendButtonPressedActionListener onGutenbergDidSendButtonPressedActionListener, AddMentionUtil addMentionUtil, OnStarterPageTemplatesTooltipShownEventListener onStarterPageTemplatesTooltipListener, boolean isDarkMode) { @@ -475,6 +486,7 @@ public void attachToContainer(ViewGroup viewGroup, mOnMediaEditorListener = onMediaEditorListener; mOnLogGutenbergUserEventListener = onLogGutenbergUserEventListener; mOnGutenbergDidRequestUnsupportedBlockFallbackListener = onGutenbergDidRequestUnsupportedBlockFallbackListener; + mOnGutenbergDidSendButtonPressedActionListener = onGutenbergDidSendButtonPressedActionListener; mAddMentionUtil = addMentionUtil; mOnStarterPageTemplatesTooltipShownListener = onStarterPageTemplatesTooltipListener; diff --git a/packages/react-native-bridge/index.js b/packages/react-native-bridge/index.js index a3bc1690ac7dd..20b7199a7a78b 100644 --- a/packages/react-native-bridge/index.js +++ b/packages/react-native-bridge/index.js @@ -24,6 +24,10 @@ export const userEvents = { editorSessionTemplatePreview: 'editor_session_template_preview', }; +export const actionButtons = { + missingBlockAlertActionButton: 'missing_block_alert_action_button', +}; + // Console polyfill from react-native export function nativeLoggingHook( message, logLevel ) { @@ -150,6 +154,15 @@ export function requestUnsupportedBlockFallback( ); } +/** + * Messages the client that an action button was pressed. + * + * @param {string} htmlContent One of the values deffined on `actionButtons` constant object. + */ +export function sendActionButtonPressedAction( buttonType ) { + RNReactNativeGutenbergBridge.actionButtonPressed( buttonType ); +} + export function requestMediaImport( url, callback ) { return RNReactNativeGutenbergBridge.requestMediaImport( url, callback ); } diff --git a/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift b/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift index b59d9cc942c4f..7f2b91db3a76a 100644 --- a/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift +++ b/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift @@ -16,6 +16,7 @@ public struct MediaInfo { public enum Capabilities: String { case mentions case unsupportedBlockEditor + case canEnableUnsupportedBlockEditor case modalLayoutPicker } @@ -227,6 +228,8 @@ public protocol GutenbergBridgeDelegate: class { /// Tells the delegate that the editor requested to set the tooltip's visibility /// - Parameter tooltipShown: Tooltip's visibility value func gutenbergDidRequestSetStarterPageTemplatesTooltipShown(_ tooltipShown: Bool) + + func gutenbergDidSendButtonPressedAction(_ buttonType: Gutenberg.ActionButtonType) } // MARK: - Optional GutenbergBridgeDelegate methods @@ -235,4 +238,5 @@ public extension GutenbergBridgeDelegate { func gutenbergDidLoad() { } func gutenbergDidLayout() { } func gutenbergDidRequestUnsupportedBlockFallback(for block: Block) { } + func gutenbergDidSendButtonPressedAction(_ buttonType: Gutenberg.ActionButtonType) { } } diff --git a/packages/react-native-bridge/ios/GutenbergWebFallback/GutenbergWebSingleBlockViewController.swift b/packages/react-native-bridge/ios/GutenbergWebFallback/GutenbergWebSingleBlockViewController.swift index 84f4ab2a79dd9..915b08f8ac0b3 100644 --- a/packages/react-native-bridge/ios/GutenbergWebFallback/GutenbergWebSingleBlockViewController.swift +++ b/packages/react-native-bridge/ios/GutenbergWebFallback/GutenbergWebSingleBlockViewController.swift @@ -54,14 +54,29 @@ open class GutenbergWebSingleBlockViewController: UIViewController { completion(request) } + /// Requests a set of JS Scripts to be added to the web view when the page has started loading. + /// - Returns: Array of all the scripts to be added open func onPageLoadScripts() -> [WKUserScript] { return [] } + /// Requests a set of JS Scripts to be added to the web view when Gutenberg has been initialized. + /// - Returns: Array of all the scripts to be added open func onGutenbergReadyScripts() -> [WKUserScript] { return [] } + /// Called when Gutenberg Web editor is loaded in the web view. + /// If overriden, is required to call super.onGutenbergReady() + open func onGutenbergReady() { + onGutenbergReadyScripts().forEach(evaluateJavascript) + evaluateJavascript(jsInjection.preventAutosavesScript) + evaluateJavascript(jsInjection.insertBlockScript) + DispatchQueue.main.async { [weak self] in + self?.removeCoverViewAnimated() + } + } + public func cleanUp() { webView.configuration.userContentController.removeAllUserScripts() FallbackJavascriptInjection.JSMessage.allCases.forEach { @@ -132,15 +147,6 @@ open class GutenbergWebSingleBlockViewController: UIViewController { private func save(_ newContent: String) { delegate?.webController(controller: self, didPressSave: block.replacingContent(with: newContent)) } - - private func onGutenbergReady() { - onGutenbergReadyScripts().forEach(evaluateJavascript) - evaluateJavascript(jsInjection.preventAutosavesScript) - evaluateJavascript(jsInjection.insertBlockScript) - DispatchQueue.main.async { [weak self] in - self?.removeCoverViewAnimated() - } - } } extension GutenbergWebSingleBlockViewController: WKNavigationDelegate { diff --git a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m index 114a86a7b8f51..9b68bf074f161 100644 --- a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m +++ b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m @@ -23,5 +23,6 @@ @interface RCT_EXTERN_MODULE(RNReactNativeGutenbergBridge, NSObject) RCT_EXTERN_METHOD(addMention:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)rejecter) RCT_EXTERN_METHOD(requestStarterPageTemplatesTooltipShown:(RCTResponseSenderBlock)callback) RCT_EXTERN_METHOD(setStarterPageTemplatesTooltipShown:(BOOL)tooltipShown) +RCT_EXTERN_METHOD(actionButtonPressed:(NSString *)buttonType) @end diff --git a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift index 589450a710902..b48726772fda3 100644 --- a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift +++ b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift @@ -288,10 +288,26 @@ public class RNReactNativeGutenbergBridge: RCTEventEmitter { self.delegate?.gutenbergDidRequestSetStarterPageTemplatesTooltipShown(tooltipShown) } + @objc + func actionButtonPressed(_ buttonType: String) { + guard let button = Gutenberg.ActionButtonType(rawValue: buttonType) else { + return + } + DispatchQueue.main.async { + self.delegate?.gutenbergDidSendButtonPressedAction(button) + } + } + } // MARK: - RCTBridgeModule delegate +public extension Gutenberg { + public enum ActionButtonType: String { + case missingBlockAlertActionButton = "missing_block_alert_action_button" + } +} + extension RNReactNativeGutenbergBridge { enum EventName: String, CaseIterable { case requestGetHtml diff --git a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java index e9dbb45a78915..66a4bfc431b87 100644 --- a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java +++ b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java @@ -171,6 +171,11 @@ public void onAddMention(Consumer onSuccess) { onSuccess.accept("matt"); } + @Override + public void gutenbergDidSendButtonPressedAction(String buttonType) { + + } + }, isDarkMode()); return new ReactNativeHost(this) { From 6325f7682a8b400cf64c5f569a17dec20797337b Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Mon, 28 Sep 2020 12:58:15 +0200 Subject: [PATCH 25/79] [Widgets Editor] Ensure all widgets are properly initialized when they're added, do not unmount widgets once they're mounted (#25645) * Ensure all widgets are properly initialized when they're added, do not unmount once mounted widgets * Resolve conflicts * Add explanatory comment --- .../src/widget-area/edit/index.js | 26 +++++-- .../block-library/src/widget-area/editor.scss | 13 +++- .../blocks/legacy-widget/edit/dom-manager.js | 71 +++++++++---------- .../src/blocks/legacy-widget/edit/index.js | 68 +++++++++--------- .../src/blocks/legacy-widget/index.js | 2 +- 5 files changed, 100 insertions(+), 80 deletions(-) diff --git a/packages/block-library/src/widget-area/edit/index.js b/packages/block-library/src/widget-area/edit/index.js index 272956acd1809..1c759d4c660d3 100644 --- a/packages/block-library/src/widget-area/edit/index.js +++ b/packages/block-library/src/widget-area/edit/index.js @@ -26,19 +26,37 @@ export default function WidgetAreaEdit( { { - setIsWidgetAreaOpen( clientId, opened ); + // This workaround is required to ensure LegacyWidget blocks are not unmounted when the panel is collapsed. + // Unmounting legacy widgets may have unintended consequences (e.g. TinyMCE not being properly reinitialized) + opened={ true } + onToggle={ () => { + setIsWidgetAreaOpen( clientId, ! isOpen ); } } + className={ isOpen ? 'widget-area-is-opened' : '' } > - + ); } + +function InnerBlocksContainer( { isOpen } ) { + const props = isOpen + ? {} + : { + hidden: true, + 'aria-hidden': true, + style: { display: 'none' }, + }; + return ( +
+ +
+ ); +} diff --git a/packages/block-library/src/widget-area/editor.scss b/packages/block-library/src/widget-area/editor.scss index 7570743c4b3d3..591625d7b0e83 100644 --- a/packages/block-library/src/widget-area/editor.scss +++ b/packages/block-library/src/widget-area/editor.scss @@ -2,6 +2,15 @@ max-width: $widget-area-width; } -.wp-block-widget-area > .components-panel__body > .block-editor-inner-blocks { - padding-top: $grid-unit-30; +.wp-block-widget-area > .components-panel__body { + &.is-opened:not(.widget-area-is-opened) { + padding: 0; + > .components-panel__body-title { + padding: 0; + margin: 0; + } + } + > .block-editor-inner-blocks { + padding-top: $grid-unit-30; + } } diff --git a/packages/edit-widgets/src/blocks/legacy-widget/edit/dom-manager.js b/packages/edit-widgets/src/blocks/legacy-widget/edit/dom-manager.js index 32708047498fb..f6abbcc2a017b 100644 --- a/packages/edit-widgets/src/blocks/legacy-widget/edit/dom-manager.js +++ b/packages/edit-widgets/src/blocks/legacy-widget/edit/dom-manager.js @@ -59,7 +59,7 @@ class LegacyWidgetEditDomManager extends Component { } render() { - const { id, idBase, number, form, isReferenceWidget } = this.props; + const { id, idBase, number, form } = this.props; return (
@@ -76,42 +76,39 @@ class LegacyWidgetEditDomManager extends Component { className="widget-content" dangerouslySetInnerHTML={ { __html: form } } /> - { isReferenceWidget && ( - <> - - - - - - - ) } + + + + + +
diff --git a/packages/edit-widgets/src/blocks/legacy-widget/edit/index.js b/packages/edit-widgets/src/blocks/legacy-widget/edit/index.js index 649d059e299b7..276fe713e0431 100644 --- a/packages/edit-widgets/src/blocks/legacy-widget/edit/index.js +++ b/packages/edit-widgets/src/blocks/legacy-widget/edit/index.js @@ -184,41 +184,37 @@ function LegacyWidgetEdit( { ); } -export default withSelect( - ( - select, - { clientId, attributes: { widgetClass, referenceWidgetName } } - ) => { - let widgetId = select( 'core/edit-widgets' ).getWidgetIdForClientId( - clientId - ); - const widget = select( 'core/edit-widgets' ).getWidget( widgetId ); - const widgetArea = select( - 'core/edit-widgets' - ).getWidgetAreaForClientId( clientId ); - const editorSettings = select( 'core/block-editor' ).getSettings(); - const { - availableLegacyWidgets, - hasPermissionsToManageWidgets, - } = editorSettings; - - let WPWidget; - if ( widgetId && availableLegacyWidgets[ widgetId ] ) { - WPWidget = availableLegacyWidgets[ widgetId ]; - } else if ( widgetClass && availableLegacyWidgets[ widgetClass ] ) { - WPWidget = availableLegacyWidgets[ widgetClass ]; - } else if ( referenceWidgetName ) { - WPWidget = availableLegacyWidgets[ referenceWidgetName ]; - widgetId = referenceWidgetName; - } +export default withSelect( ( select, { clientId, attributes } ) => { + const { widgetClass, referenceWidgetName } = attributes; + let widgetId = select( 'core/edit-widgets' ).getWidgetIdForClientId( + clientId + ); + const widget = select( 'core/edit-widgets' ).getWidget( widgetId ); + const widgetArea = select( 'core/edit-widgets' ).getWidgetAreaForClientId( + clientId + ); + const editorSettings = select( 'core/block-editor' ).getSettings(); + const { + availableLegacyWidgets, + hasPermissionsToManageWidgets, + } = editorSettings; - return { - hasPermissionsToManageWidgets, - availableLegacyWidgets, - widgetId, - widgetAreaId: widgetArea?.id, - WPWidget, - prerenderedEditForm: widget ? widget.rendered_form : '', - }; + let WPWidget; + if ( widgetId && availableLegacyWidgets[ widgetId ] ) { + WPWidget = availableLegacyWidgets[ widgetId ]; + } else if ( widgetClass && availableLegacyWidgets[ widgetClass ] ) { + WPWidget = availableLegacyWidgets[ widgetClass ]; + } else if ( referenceWidgetName ) { + WPWidget = availableLegacyWidgets[ referenceWidgetName ]; + widgetId = referenceWidgetName; } -)( LegacyWidgetEdit ); + + return { + hasPermissionsToManageWidgets, + availableLegacyWidgets, + widgetId, + widgetAreaId: widgetArea?.id, + WPWidget, + prerenderedEditForm: widget ? widget.rendered_form : '', + }; +} )( LegacyWidgetEdit ); diff --git a/packages/edit-widgets/src/blocks/legacy-widget/index.js b/packages/edit-widgets/src/blocks/legacy-widget/index.js index 79c901c274308..bf9ed2abe45a9 100644 --- a/packages/edit-widgets/src/blocks/legacy-widget/index.js +++ b/packages/edit-widgets/src/blocks/legacy-widget/index.js @@ -73,7 +73,7 @@ function legacyWidgetToBlockVariation( className, widget ) { }; } else { blockVariation.attributes = { - id_base: widget.id_base, + idBase: widget.id_base, widgetClass: className, instance: {}, }; From 78dde89b370476502ab69c664084c29001b50be5 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Mon, 28 Sep 2020 07:14:07 -0600 Subject: [PATCH 26/79] Add post author selector enhanced with ComboboxControl (#23237) Co-authored-by: epiqueras Co-authored-by: Enrique Piqueras Co-authored-by: Noah Allen Co-authored-by: Riad Benguella Co-authored-by: jasmussen --- .../block-library/src/post-author/edit.js | 4 +- .../components/src/combobox-control/index.js | 28 +--- .../src/combobox-control/stories/index.js | 2 +- .../src/combobox-control/style.scss | 6 +- packages/core-data/src/selectors.js | 3 + .../src/components/post-author/check.js | 4 +- .../src/components/post-author/index.js | 147 +++++++++++------- .../src/components/post-author/test/index.js | 64 -------- 8 files changed, 114 insertions(+), 144 deletions(-) delete mode 100644 packages/editor/src/components/post-author/test/index.js diff --git a/packages/block-library/src/post-author/edit.js b/packages/block-library/src/post-author/edit.js index 3a159fbf76be3..cedbede9f5de6 100644 --- a/packages/block-library/src/post-author/edit.js +++ b/packages/block-library/src/post-author/edit.js @@ -23,7 +23,7 @@ function PostAuthorEdit( { isSelected, context, attributes, setAttributes } ) { const { authorId, authorDetails, authors } = useSelect( ( select ) => { - const { getEditedEntityRecord, getUser, getAuthors } = select( + const { getEditedEntityRecord, getUser, getUsers } = select( 'core' ); const _authorId = getEditedEntityRecord( @@ -35,7 +35,7 @@ function PostAuthorEdit( { isSelected, context, attributes, setAttributes } ) { return { authorId: _authorId, authorDetails: _authorId ? getUser( _authorId ) : null, - authors: getAuthors(), + authors: getUsers( { who: 'authors' } ), }; }, [ postType, postId ] diff --git a/packages/components/src/combobox-control/index.js b/packages/components/src/combobox-control/index.js index e3ee035f86fdf..fcc8d332e41bd 100644 --- a/packages/components/src/combobox-control/index.js +++ b/packages/components/src/combobox-control/index.js @@ -19,7 +19,7 @@ function ComboboxControl( { label, options, onChange, - onInputChange: onInputChangeProp = () => {}, + onFilterValueChange, hideLabelFromVision, help, messages = { @@ -31,11 +31,10 @@ function ComboboxControl( { const [ isExpanded, setIsExpanded ] = useState( false ); const [ inputValue, setInputValue ] = useState( '' ); const inputContainer = useRef(); + const currentOption = options.find( ( option ) => option.value === value ); + const currentLabel = currentOption?.label ?? ''; const matchingSuggestions = useMemo( () => { - if ( ! inputValue || inputValue.length === 0 ) { - return options.filter( ( option ) => option.value !== value ); - } const startsWithMatch = []; const containsMatch = []; const match = inputValue.toLocaleLowerCase(); @@ -55,7 +54,7 @@ function ComboboxControl( { onChange( newSelectedSuggestion.value ); speak( messages.selected, 'assertive' ); setSelectedSuggestion( newSelectedSuggestion ); - setInputValue( selectedSuggestion.label ); + setInputValue( '' ); setIsExpanded( false ); }; @@ -109,33 +108,20 @@ function ComboboxControl( { // TODO: TokenInput should preferably forward ref inputContainer.current.input.focus(); setIsExpanded( true ); + onFilterValueChange( '' ); }; const onBlur = () => { - const currentOption = options.find( - ( option ) => option.value === value - ); - setInputValue( currentOption?.label ?? '' ); setIsExpanded( false ); }; const onInputChange = ( event ) => { const text = event.value; setInputValue( text ); - onInputChangeProp( text ); + onFilterValueChange( text ); setIsExpanded( true ); }; - // Reset the value on change - useEffect( () => { - if ( matchingSuggestions.indexOf( selectedSuggestion ) === -1 ) { - setSelectedSuggestion( null ); - } - if ( ! inputValue || matchingSuggestions.length === 0 ) { - onChange( null ); - } - }, [ matchingSuggestions, inputValue, value ] ); - // Announcements useEffect( () => { const hasMatchingSuggestions = matchingSuggestions.length > 0; @@ -179,7 +165,7 @@ function ComboboxControl( { className="components-combobox-control__input" instanceId={ instanceId } ref={ inputContainer } - value={ inputValue } + value={ isExpanded ? inputValue : currentLabel } onBlur={ onBlur } isExpanded={ isExpanded } selectedSuggestionIndex={ matchingSuggestions.indexOf( diff --git a/packages/components/src/combobox-control/stories/index.js b/packages/components/src/combobox-control/stories/index.js index dbe474fb2b6d0..a7f250e38573e 100644 --- a/packages/components/src/combobox-control/stories/index.js +++ b/packages/components/src/combobox-control/stories/index.js @@ -276,7 +276,7 @@ function ComboboxControlWithState() { onChange={ setValue } label="Select a country" options={ filteredOptions } - onInputChange={ ( filter ) => + onFilterValueChange={ ( filter ) => setFilteredOptions( countries .filter( ( country ) => diff --git a/packages/components/src/combobox-control/style.scss b/packages/components/src/combobox-control/style.scss index 7015bca4e3a09..2dbfcb3b70c35 100644 --- a/packages/components/src/combobox-control/style.scss +++ b/packages/components/src/combobox-control/style.scss @@ -2,10 +2,14 @@ width: 100%; } -.components-combobox-control__input { +input.components-combobox-control__input[type="text"] { width: 100%; border: none; box-shadow: none; + padding: 2px; + margin: 0; + line-height: inherit; + min-height: auto; &:focus { outline: none; diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index 8eaedd8dbf1da..cd56f12bfc052 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -44,6 +44,9 @@ export const isRequestingEmbedPreview = createRegistrySelector( * @return {Array} Authors list. */ export function getAuthors( state ) { + deprecated( "select( 'core' ).getAuthors()", { + alternative: "select( 'core' ).getUsers({ who: 'authors' })", + } ); return getUserQueryResults( state, 'authors' ); } diff --git a/packages/editor/src/components/post-author/check.js b/packages/editor/src/components/post-author/check.js index 45475476545a0..8ee69ff4826c9 100644 --- a/packages/editor/src/components/post-author/check.js +++ b/packages/editor/src/components/post-author/check.js @@ -19,7 +19,7 @@ export function PostAuthorCheck( { authors, children, } ) { - if ( ! hasAssignAuthorAction || authors.length < 2 ) { + if ( ! hasAssignAuthorAction || ! authors || authors.length < 2 ) { return null; } @@ -40,7 +40,7 @@ export default compose( [ false ), postType: select( 'core/editor' ).getCurrentPostType(), - authors: select( 'core' ).getAuthors(), + authors: select( 'core' ).getUsers( { who: 'authors' } ), }; } ), withInstanceId, diff --git a/packages/editor/src/components/post-author/index.js b/packages/editor/src/components/post-author/index.js index b99f3f80ce451..d97a6223fd232 100644 --- a/packages/editor/src/components/post-author/index.js +++ b/packages/editor/src/components/post-author/index.js @@ -1,71 +1,112 @@ +/** + * External dependencies + */ +import { debounce } from 'lodash'; + /** * WordPress dependencies */ +import { useState, useMemo, useEffect } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { withInstanceId, compose } from '@wordpress/compose'; -import { Component } from '@wordpress/element'; -import { withSelect, withDispatch } from '@wordpress/data'; -import { decodeEntities } from '@wordpress/html-entities'; +import { ComboboxControl } from '@wordpress/components'; /** * Internal dependencies */ import PostAuthorCheck from './check'; -export class PostAuthor extends Component { - constructor() { - super( ...arguments ); +function PostAuthor() { + const [ fieldValue, setFieldValue ] = useState(); - this.setAuthorId = this.setAuthorId.bind( this ); - } + const { authorId, isLoading, authors, postAuthor } = useSelect( + ( select ) => { + const { getUser, getUsers, isResolving } = select( 'core' ); + const { getEditedPostAttribute } = select( 'core/editor' ); + const author = getUser( getEditedPostAttribute( 'author' ) ); + const query = + ! fieldValue || '' === fieldValue ? {} : { search: fieldValue }; + return { + authorId: getEditedPostAttribute( 'author' ), + postAuthor: author, + authors: getUsers( { who: 'authors', ...query } ), + isLoading: isResolving( 'core', 'getUsers', [ + { search: fieldValue, who: 'authors' }, + ] ), + }; + }, + [ fieldValue ] + ); + const { editPost } = useDispatch( 'core/editor' ); - setAuthorId( event ) { - const { onUpdateAuthor } = this.props; - const { value } = event.target; - onUpdateAuthor( Number( value ) ); - } + const authorOptions = useMemo( () => { + const fetchedAuthors = ( authors ?? [] ).map( ( author ) => { + return { + value: author.id, + label: author.name, + }; + } ); + + // Ensure the current author is included in the dropdown list. + const foundAuthor = fetchedAuthors.findIndex( + ( { value } ) => postAuthor?.id === value + ); - render() { - const { postAuthor, instanceId, authors } = this.props; - const selectId = 'post-author-selector-' + instanceId; + if ( foundAuthor < 0 && postAuthor ) { + return [ + { value: postAuthor.id, label: postAuthor.name }, + ...fetchedAuthors, + ]; + } - // Disable reason: A select with an onchange throws a warning + return fetchedAuthors; + }, [ authors, postAuthor ] ); - /* eslint-disable jsx-a11y/no-onchange */ - return ( - - - - - ); - /* eslint-enable jsx-a11y/no-onchange */ + // Initializes the post author properly + // Also ensures external changes are reflected. + useEffect( () => { + if ( postAuthor ) { + setFieldValue( postAuthor.name ); + } + }, [ postAuthor ] ); + + /** + * Handle author selection. + * + * @param {number} postAuthorId The selected Author. + */ + const handleSelect = ( postAuthorId ) => { + if ( ! postAuthorId ) { + return; + } + editPost( { author: postAuthorId } ); + }; + + /** + * Handle user input. + * + * @param {string} inputValue The current value of the input field. + */ + const handleKeydown = ( inputValue ) => { + setFieldValue( inputValue ); + }; + + if ( ! postAuthor ) { + return null; } + + return ( + + + + ); } -export default compose( [ - withSelect( ( select ) => { - return { - postAuthor: select( 'core/editor' ).getEditedPostAttribute( - 'author' - ), - authors: select( 'core' ).getAuthors(), - }; - } ), - withDispatch( ( dispatch ) => ( { - onUpdateAuthor( author ) { - dispatch( 'core/editor' ).editPost( { author } ); - }, - } ) ), - withInstanceId, -] )( PostAuthor ); +export default PostAuthor; diff --git a/packages/editor/src/components/post-author/test/index.js b/packages/editor/src/components/post-author/test/index.js deleted file mode 100644 index 368be4fda7d26..0000000000000 --- a/packages/editor/src/components/post-author/test/index.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * External dependencies - */ -import { shallow } from 'enzyme'; - -/** - * Internal dependencies - */ -import { PostAuthor } from '../'; - -describe( 'PostAuthor', () => { - const authors = [ - { - id: 1, - name: 'admin', - capabilities: { - level_1: true, - }, - }, - { - id: 2, - name: 'subscriber', - capabilities: { - level_0: true, - }, - }, - { - id: 3, - name: 'andrew', - capabilities: { - level_1: true, - }, - }, - ]; - - const user = { - data: { - capabilities: { - publish_posts: true, - }, - }, - }; - - describe( '#render()', () => { - it( 'should update author', () => { - const onUpdateAuthor = jest.fn(); - const wrapper = shallow( - - ); - - wrapper.find( 'select' ).simulate( 'change', { - target: { - value: '3', - }, - } ); - - expect( onUpdateAuthor ).toHaveBeenCalledWith( 3 ); - } ); - } ); -} ); From 3f47cbc6d1283ce689fa32dee60e6e4c6a09eb98 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 28 Sep 2020 10:42:26 -0400 Subject: [PATCH 27/79] Block Directory: Add developer docs to readme (#25591) --- packages/block-directory/README.md | 240 ++++++++++++++++++ packages/block-directory/src/store/actions.js | 18 +- .../block-directory/src/store/selectors.js | 22 +- 3 files changed, 261 insertions(+), 19 deletions(-) diff --git a/packages/block-directory/README.md b/packages/block-directory/README.md index 16ef9ac8bc8d3..77b6f9afc551b 100644 --- a/packages/block-directory/README.md +++ b/packages/block-directory/README.md @@ -14,4 +14,244 @@ npm install @wordpress/block-directory --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ +## Usage + +This package builds a standalone JS file. When loaded on a page with the block editor, it extends the block inserter to search for blocks from WordPress.org. + +To do this, it uses the `__experimentalInserterMenuExtension`, a slot-fill area hooked into the block types list. When the user runs a search and there are no results currently installed, it fires off a request to WordPress.org for matching blocks. These are listed for the user to install with a one-click process that [installs, activates, and injects the block into the post.](./src/store/actions.js#L49) When the post is saved, if the block was not used, it will be [silently uninstalled](./src/store/actions.js#L129) to avoid clutter. + +See also the API endpoints for searching WordPress.org: `/wp/v2/block-directory/search`, and installing & activating plugins: `/wp/v2/plugins/`. + +## Actions + +The following set of dispatching action creators are available on the object returned by `wp.data.dispatch( 'core/block-directory' )`: + + + +# **addInstalledBlockType** + +Returns an action object used to add a block type to the "newly installed" +tracking list. + +_Parameters_ + +- _item_ `Object`: The block item with the block id and name. + +_Returns_ + +- `Object`: Action object. + +# **clearErrorNotice** + +Sets the error notice to empty for specific block. + +_Parameters_ + +- _blockId_ `string`: The ID of the block plugin. eg: my-block + +_Returns_ + +- `Object`: Action object. + +# **fetchDownloadableBlocks** + +Returns an action object used in signalling that the downloadable blocks +have been requested and are loading. + +_Parameters_ + +- _filterValue_ `string`: Search string. + +_Returns_ + +- `Object`: Action object. + +# **installBlockType** + +Action triggered to install a block plugin. + +_Parameters_ + +- _block_ `Object`: The block item returned by search. + +_Returns_ + +- `boolean`: Whether the block was successfully installed & loaded. + +# **receiveDownloadableBlocks** + +Returns an action object used in signalling that the downloadable blocks +have been updated. + +_Parameters_ + +- _downloadableBlocks_ `Array`: Downloadable blocks. +- _filterValue_ `string`: Search string. + +_Returns_ + +- `Object`: Action object. + +# **removeInstalledBlockType** + +Returns an action object used to remove a block type from the "newly installed" +tracking list. + +_Parameters_ + +- _item_ `string`: The block item with the block id and name. + +_Returns_ + +- `Object`: Action object. + +# **setErrorNotice** + +Sets an error notice to be displayed to the user for a given block. + +_Parameters_ + +- _blockId_ `string`: The ID of the block plugin. eg: my-block +- _message_ `string`: The message shown in the notice. +- _isFatal_ `boolean`: Whether the user can recover from the error. + +_Returns_ + +- `Object`: Action object. + +# **setIsInstalling** + +Returns an action object used to indicate install in progress. + +_Parameters_ + +- _blockId_ `string`: +- _isInstalling_ `boolean`: + +_Returns_ + +- `Object`: Action object. + +# **uninstallBlockType** + +Action triggered to uninstall a block plugin. + +_Parameters_ + +- _block_ `Object`: The blockType object. + + + +## Selectors + +The following selectors are available on the object returned by `wp.data.select( 'core/block-directory' )`: + + + +# **getDownloadableBlocks** + +Returns the available uninstalled blocks. + +_Parameters_ + +- _state_ `Object`: Global application state. +- _filterValue_ `string`: Search string. + +_Returns_ + +- `Array`: Downloadable blocks. + +# **getErrorNoticeForBlock** + +Returns the error notice for a given block. + +_Parameters_ + +- _state_ `Object`: Global application state. +- _blockId_ `string`: The ID of the block plugin. eg: my-block + +_Returns_ + +- `(string|boolean)`: The error text, or false if no error. + +# **getErrorNotices** + +Returns all block error notices. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `Object`: Object with error notices. + +# **getInstalledBlockTypes** + +Returns the block types that have been installed on the server in this +session. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `Array`: Block type items + +# **getNewBlockTypes** + +Returns block types that have been installed on the server and used in the +current post. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `Array`: Block type items. + +# **getUnusedBlockTypes** + +Returns the block types that have been installed on the server but are not +used in the current post. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `Array`: Block type items. + +# **isInstalling** + +Returns true if a block plugin install is in progress. + +_Parameters_ + +- _state_ `Object`: Global application state. +- _blockId_ `string`: Id of the block. + +_Returns_ + +- `boolean`: Whether this block is currently being installed. + +# **isRequestingDownloadableBlocks** + +Returns true if application is requesting for downloadable blocks. + +_Parameters_ + +- _state_ `Object`: Global application state. +- _filterValue_ `string`: Search string. + +_Returns_ + +- `boolean`: Whether a request is in progress for the blocks list. + + + +

Code is Poetry.

diff --git a/packages/block-directory/src/store/actions.js b/packages/block-directory/src/store/actions.js index 9f1e96d5db26b..905c46590a654 100644 --- a/packages/block-directory/src/store/actions.js +++ b/packages/block-directory/src/store/actions.js @@ -12,7 +12,7 @@ import getPluginUrl from './utils/get-plugin-url'; /** * Returns an action object used in signalling that the downloadable blocks - * have been requested and is loading. + * have been requested and are loading. * * @param {string} filterValue Search string. * @@ -150,7 +150,8 @@ export function* uninstallBlockType( block ) { } /** - * Returns an action object used to add a newly installed block type. + * Returns an action object used to add a block type to the "newly installed" + * tracking list. * * @param {Object} item The block item with the block id and name. * @@ -164,7 +165,8 @@ export function addInstalledBlockType( item ) { } /** - * Returns an action object used to remove a newly installed block type. + * Returns an action object used to remove a block type from the "newly installed" + * tracking list. * * @param {string} item The block item with the block id and name. * @@ -178,7 +180,7 @@ export function removeInstalledBlockType( item ) { } /** - * Returns an action object used to indicate install in progress + * Returns an action object used to indicate install in progress. * * @param {string} blockId * @param {boolean} isInstalling @@ -194,11 +196,11 @@ export function setIsInstalling( blockId, isInstalling ) { } /** - * Sets an error notice string to be displayed to the user + * Sets an error notice to be displayed to the user for a given block. * - * @param {string} blockId The ID of the block plugin. eg: my-block + * @param {string} blockId The ID of the block plugin. eg: my-block * @param {string} message The message shown in the notice. - * @param {boolean} isFatal Whether the user can recover from the error + * @param {boolean} isFatal Whether the user can recover from the error. * * @return {Object} Action object. */ @@ -212,7 +214,7 @@ export function setErrorNotice( blockId, message, isFatal = false ) { } /** - * Sets the error notice to empty for specific block + * Sets the error notice to empty for specific block. * * @param {string} blockId The ID of the block plugin. eg: my-block * diff --git a/packages/block-directory/src/store/selectors.js b/packages/block-directory/src/store/selectors.js index 2d14bb029467f..dbf94678436e8 100644 --- a/packages/block-directory/src/store/selectors.js +++ b/packages/block-directory/src/store/selectors.js @@ -11,11 +11,10 @@ import hasBlockType from './utils/has-block-type'; /** * Returns true if application is requesting for downloadable blocks. * - * @param {Object} state Global application state. + * @param {Object} state Global application state. * @param {string} filterValue Search string. * - * - * @return {Array} Downloadable blocks + * @return {boolean} Whether a request is in progress for the blocks list. */ export function isRequestingDownloadableBlocks( state, filterValue ) { if ( @@ -28,12 +27,12 @@ export function isRequestingDownloadableBlocks( state, filterValue ) { } /** - * Returns the available uninstalled blocks + * Returns the available uninstalled blocks. * * @param {Object} state Global application state. * @param {string} filterValue Search string. * - * @return {Array} Downloadable blocks + * @return {Array} Downloadable blocks. */ export function getDownloadableBlocks( state, filterValue ) { if ( @@ -46,11 +45,12 @@ export function getDownloadableBlocks( state, filterValue ) { } /** - * Returns the block types that have been installed on the server. + * Returns the block types that have been installed on the server in this + * session. * * @param {Object} state Global application state. * - * @return {Array} Block type items. + * @return {Array} Block type items */ export function getInstalledBlockTypes( state ) { return state.blockManagement.installedBlockTypes; @@ -95,19 +95,19 @@ export const getUnusedBlockTypes = createRegistrySelector( ); /** - * Returns true if application is calling install endpoint. + * Returns true if a block plugin install is in progress. * - * @param {Object} state Global application state. + * @param {Object} state Global application state. * @param {string} blockId Id of the block. * - * @return {boolean} Whether its currently installing + * @return {boolean} Whether this block is currently being installed. */ export function isInstalling( state, blockId ) { return state.blockManagement.isInstalling[ blockId ] || false; } /** - * Returns the error notices + * Returns all block error notices. * * @param {Object} state Global application state. * From a63cc627fa9de3ba9cfc3fcdf5ac07e0549dd166 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 28 Sep 2020 10:43:01 -0400 Subject: [PATCH 28/79] Block Directory: Switch to `blocks.registerBlockType` filter (#25264) * Block Directory: Switch to `blocks.registerBlockType` filter This lets us avoid adding a new filter specific to the missing block * Check for html & html block support before offering the convert to HTML option * Remove the filter from `MissingEdit` * Add `convertToHTML` function --- .../src/plugins/filter-missing/index.js | 66 ------------- .../src/plugins/get-install-missing/index.js | 93 +++++++++++++++++++ .../install-button.js | 0 packages/block-directory/src/plugins/index.js | 15 ++- packages/block-library/src/missing/edit.js | 32 +++---- 5 files changed, 118 insertions(+), 88 deletions(-) delete mode 100644 packages/block-directory/src/plugins/filter-missing/index.js create mode 100644 packages/block-directory/src/plugins/get-install-missing/index.js rename packages/block-directory/src/plugins/{filter-missing => get-install-missing}/install-button.js (100%) diff --git a/packages/block-directory/src/plugins/filter-missing/index.js b/packages/block-directory/src/plugins/filter-missing/index.js deleted file mode 100644 index 2b958ad54eb51..0000000000000 --- a/packages/block-directory/src/plugins/filter-missing/index.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * WordPress dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; -import { RawHTML } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; -import { Warning } from '@wordpress/block-editor'; - -/** - * Internal dependencies - */ -import InstallButton from './install-button'; - -const filterMissing = ( OriginalComponent ) => ( props ) => { - const { originalName, originalUndelimitedContent } = props.attributes; - const { block, hasPermission } = useSelect( - ( select ) => { - const { getDownloadableBlocks } = select( 'core/block-directory' ); - const blocks = getDownloadableBlocks( - 'block:' + originalName - ).filter( ( { name } ) => originalName === name ); - return { - hasPermission: select( 'core' ).canUser( - 'read', - 'block-directory/search' - ), - block: blocks.length && blocks[ 0 ], - }; - }, - [ originalName ] - ); - - if ( ! hasPermission || ! block ) { - return ; - } - - const actions = [ - , - , - ]; - - return ( - <> - - { sprintf( - /* translators: %s: block name */ - __( - 'Your site doesn’t include support for the %s block. You can try installing the block, convert it to a Custom HTML block, or remove it entirely.' - ), - block.title || originalName - ) } - - { originalUndelimitedContent } - - ); -}; - -export default filterMissing; diff --git a/packages/block-directory/src/plugins/get-install-missing/index.js b/packages/block-directory/src/plugins/get-install-missing/index.js new file mode 100644 index 0000000000000..da4337e69249c --- /dev/null +++ b/packages/block-directory/src/plugins/get-install-missing/index.js @@ -0,0 +1,93 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; +import { createBlock, getBlockType } from '@wordpress/blocks'; +import { RawHTML } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { Warning } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import InstallButton from './install-button'; + +const getInstallMissing = ( OriginalComponent ) => ( props ) => { + const { originalName, originalUndelimitedContent } = props.attributes; + // Disable reason: This is a valid component, but it's mistaken for a callback. + // eslint-disable-next-line react-hooks/rules-of-hooks + const { block, hasPermission } = useSelect( + ( select ) => { + const { getDownloadableBlocks } = select( 'core/block-directory' ); + const blocks = getDownloadableBlocks( + 'block:' + originalName + ).filter( ( { name } ) => originalName === name ); + return { + hasPermission: select( 'core' ).canUser( + 'read', + 'block-directory/search' + ), + block: blocks.length && blocks[ 0 ], + }; + }, + [ originalName ] + ); + + const { replaceBlock } = useDispatch( 'core/block-editor' ); + const convertToHTML = () => { + replaceBlock( + props.clientId, + createBlock( 'core/html', { + content: originalUndelimitedContent, + } ) + ); + }; + + if ( ! hasPermission || ! block ) { + return ; + } + + const hasContent = !! originalUndelimitedContent; + const hasHTMLBlock = getBlockType( 'core/html' ); + + let messageHTML = sprintf( + /* translators: %s: block name */ + __( + 'Your site doesn’t include support for the %s block. You can try installing the block or remove it entirely.' + ), + block.title || originalName + ); + const actions = [ + , + ]; + + if ( hasContent && hasHTMLBlock ) { + messageHTML = sprintf( + /* translators: %s: block name */ + __( + 'Your site doesn’t include support for the %s block. You can try installing the block, convert it to a Custom HTML block, or remove it entirely.' + ), + block.title || originalName + ); + actions.push( + + ); + } + + return ( + <> + { messageHTML } + { originalUndelimitedContent } + + ); +}; + +export default getInstallMissing; diff --git a/packages/block-directory/src/plugins/filter-missing/install-button.js b/packages/block-directory/src/plugins/get-install-missing/install-button.js similarity index 100% rename from packages/block-directory/src/plugins/filter-missing/install-button.js rename to packages/block-directory/src/plugins/get-install-missing/install-button.js diff --git a/packages/block-directory/src/plugins/index.js b/packages/block-directory/src/plugins/index.js index beb9aac416e31..283ff0ea4d271 100644 --- a/packages/block-directory/src/plugins/index.js +++ b/packages/block-directory/src/plugins/index.js @@ -10,7 +10,7 @@ import { addFilter } from '@wordpress/hooks'; import AutoBlockUninstaller from '../components/auto-block-uninstaller'; import InserterMenuDownloadableBlocksPanel from './inserter-menu-downloadable-blocks-panel'; import InstalledBlocksPrePublishPanel from './installed-blocks-pre-publish-panel'; -import filterMissing from './filter-missing'; +import getInstallMissing from './get-install-missing'; registerPlugin( 'block-directory', { render() { @@ -25,7 +25,14 @@ registerPlugin( 'block-directory', { } ); addFilter( - 'editor.missingEdit', - 'block-directory/install-missing', - filterMissing + 'blocks.registerBlockType', + 'block-directory/fallback', + ( settings, name ) => { + if ( name !== 'core/missing' ) { + return settings; + } + settings.edit = getInstallMissing( settings.edit ); + + return settings; + } ); diff --git a/packages/block-library/src/missing/edit.js b/packages/block-library/src/missing/edit.js index 5727e9eee3a87..e3662cace1a2e 100644 --- a/packages/block-library/src/missing/edit.js +++ b/packages/block-library/src/missing/edit.js @@ -2,9 +2,8 @@ * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { compose } from '@wordpress/compose'; import { RawHTML } from '@wordpress/element'; -import { Button, withFilters } from '@wordpress/components'; +import { Button } from '@wordpress/components'; import { getBlockType, createBlock } from '@wordpress/blocks'; import { withDispatch } from '@wordpress/data'; import { Warning } from '@wordpress/block-editor'; @@ -47,21 +46,18 @@ function MissingBlockWarning( { attributes, convertToHTML } ) { ); } -const MissingEdit = compose( - withDispatch( ( dispatch, { clientId, attributes } ) => { - const { replaceBlock } = dispatch( 'core/block-editor' ); - return { - convertToHTML() { - replaceBlock( - clientId, - createBlock( 'core/html', { - content: attributes.originalUndelimitedContent, - } ) - ); - }, - }; - } ), - withFilters( 'editor.missingEdit' ) -)( MissingBlockWarning ); +const MissingEdit = withDispatch( ( dispatch, { clientId, attributes } ) => { + const { replaceBlock } = dispatch( 'core/block-editor' ); + return { + convertToHTML() { + replaceBlock( + clientId, + createBlock( 'core/html', { + content: attributes.originalUndelimitedContent, + } ) + ); + }, + }; +} )( MissingBlockWarning ); export default MissingEdit; From 5d9ceff0ea5f62d1adcc8acaeca2518b966d5e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Mon, 28 Sep 2020 19:08:14 +0300 Subject: [PATCH 29/79] Fix ref (#25679) --- .../block-editor/src/components/block-list/index.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index a4dc285582a6c..22ede3bc48e67 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -69,9 +69,12 @@ function BlockList( isDraggingBlocks, } = useSelect( selector, [ rootClientId ] ); + const fallbackRef = useRef(); + const element = __experimentalPassedProps.ref || ref || fallbackRef; + const Container = rootClientId ? __experimentalTagName : RootContainer; const dropTargetIndex = useBlockDropZone( { - element: ref, + element, rootClientId, } ); @@ -80,8 +83,8 @@ function BlockList( return ( { - const fallbackRef = useRef(); return ( - + ); } ); From 5bc76b4c6c9a88db961c4123e50d0417d08c5d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Mon, 28 Sep 2020 19:43:57 +0300 Subject: [PATCH 30/79] RichText: simplify withFormatTypes as hook (#23145) * RichText: change withFormatTypes to hook * Try rewrite * fix dispatch * Simplify --- packages/data/src/index.js | 5 +- packages/rich-text/src/component/index.js | 143 ++++++++--------- .../src/component/use-format-types.js | 102 ++++++++++++ .../src/component/with-format-types.js | 150 ------------------ 4 files changed, 167 insertions(+), 233 deletions(-) create mode 100644 packages/rich-text/src/component/use-format-types.js delete mode 100644 packages/rich-text/src/component/with-format-types.js diff --git a/packages/data/src/index.js b/packages/data/src/index.js index b4f2f71e6319b..b0f1156db81d9 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -18,10 +18,7 @@ export { useRegistry, } from './components/registry-provider'; export { default as useSelect } from './components/use-select'; -export { - useDispatch, - useDispatchWithMap as __unstableUseDispatchWithMap, -} from './components/use-dispatch'; +export { useDispatch } from './components/use-dispatch'; export { AsyncModeProvider } from './components/async-mode-provider'; export { createRegistry } from './registry'; export { createRegistrySelector, createRegistryControl } from './factory'; diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 57e8a794cb814..816c2d167d3cf 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { find, isNil, pickBy, startsWith } from 'lodash'; +import { find, isNil } from 'lodash'; /** * WordPress dependencies @@ -42,7 +42,7 @@ import { getActiveFormats } from '../get-active-formats'; import { updateFormats } from '../update-formats'; import { removeLineSeparator } from '../remove-line-separator'; import { isEmptyLine } from '../is-empty'; -import withFormatTypes from './with-format-types'; +import { useFormatTypes } from './use-format-types'; import { useBoundaryStyle } from './use-boundary-style'; import { useInlineWarning } from './use-inline-warning'; import { insert } from '../insert'; @@ -95,19 +95,12 @@ const defaultStyle = { whiteSpace }; const EMPTY_ACTIVE_FORMATS = []; -function createPrepareEditableTree( props, prefix ) { - const fns = Object.keys( props ).reduce( ( accumulator, key ) => { - if ( key.startsWith( prefix ) ) { - accumulator.push( props[ key ] ); - } - - return accumulator; - }, [] ); - +function createPrepareEditableTree( fns ) { return ( value ) => - fns.reduce( ( accumulator, fn ) => { - return fn( accumulator, value.text ); - }, value.formats ); + fns.reduce( + ( accumulator, fn ) => fn( accumulator, value.text ), + value.formats + ); } /** @@ -137,46 +130,59 @@ function fixPlaceholderSelection( defaultView ) { selection.collapseToStart(); } -function RichText( { - tagName: TagName = 'div', - value = '', - selectionStart, - selectionEnd, - children, - allowedFormats, - withoutInteractiveFormatting, - formatTypes, - style, - className, - placeholder, - disabled, - preserveWhiteSpace, - onPaste, - format = 'string', - onDelete, - onEnter, - onSelectionChange, - onChange, - unstableOnFocus: onFocus, - setFocusedElement, - instanceId, - __unstableMultilineTag: multilineTag, - __unstableMultilineRootTag: multilineRootTag, - __unstableDisableFormats: disableFormats, - __unstableDidAutomaticChange: didAutomaticChange, - __unstableInputRule: inputRule, - __unstableMarkAutomaticChange: markAutomaticChange, - __unstableAllowPrefixTransformations: allowPrefixTransformations, - __unstableUndo: undo, - __unstableIsCaretWithinFormattedText: isCaretWithinFormattedText, - __unstableOnEnterFormattedText: onEnterFormattedText, - __unstableOnExitFormattedText: onExitFormattedText, - __unstableOnCreateUndoLevel: onCreateUndoLevel, - __unstableIsSelected: isSelected, - forwardedRef: ref, - ...remainingProps -} ) { +function RichText( + { + tagName: TagName = 'div', + value = '', + selectionStart, + selectionEnd, + children, + allowedFormats, + withoutInteractiveFormatting, + style, + className, + placeholder, + disabled, + preserveWhiteSpace, + onPaste, + format = 'string', + onDelete, + onEnter, + onSelectionChange, + onChange, + unstableOnFocus: onFocus, + setFocusedElement, + instanceId, + clientId, + identifier, + __unstableMultilineTag: multilineTag, + __unstableMultilineRootTag: multilineRootTag, + __unstableDisableFormats: disableFormats, + __unstableDidAutomaticChange: didAutomaticChange, + __unstableInputRule: inputRule, + __unstableMarkAutomaticChange: markAutomaticChange, + __unstableAllowPrefixTransformations: allowPrefixTransformations, + __unstableUndo: undo, + __unstableIsCaretWithinFormattedText: isCaretWithinFormattedText, + __unstableOnEnterFormattedText: onEnterFormattedText, + __unstableOnExitFormattedText: onExitFormattedText, + __unstableOnCreateUndoLevel: onCreateUndoLevel, + __unstableIsSelected: isSelected, + ...remainingProps + }, + ref +) { const [ activeFormats = [], setActiveFormats ] = useState(); + const { + formatTypes, + prepareHandlers, + valueHandlers, + changeHandlers, + dependencies, + } = useFormatTypes( { + clientId, + identifier, + } ); // For backward compatibility, fall back to tagName if it's a string. // tagName can now be a component for light blocks. @@ -212,10 +218,7 @@ function RichText( { return string; } - const prepare = createPrepareEditableTree( - remainingProps, - 'format_value_functions' - ); + const prepare = createPrepareEditableTree( valueHandlers ); const result = create( { html: string, @@ -306,10 +309,7 @@ function RichText( { multilineTag, multilineWrapperTags: multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, - prepareEditableTree: createPrepareEditableTree( - remainingProps, - 'format_prepare_functions' - ), + prepareEditableTree: createPrepareEditableTree( prepareHandlers ), __unstableDomOnly: domOnly, placeholder, } ); @@ -913,9 +913,6 @@ function RichText( { applyRecord( newRecord ); const { start, end, activeFormats: newActiveFormats = [] } = newRecord; - const changeHandlers = pickBy( remainingProps, ( v, key ) => - key.startsWith( 'format_on_change_functions_' ) - ); Object.values( changeHandlers ).forEach( ( changeHandler ) => { changeHandler( newRecord.formats, newRecord.text ); @@ -1075,15 +1072,11 @@ function RichText( { } }, [ selectionStart, selectionEnd, isSelected ] ); - const prefix = 'format_prepare_props_'; - const predicate = ( v, key ) => key.startsWith( prefix ); - const prepareProps = pickBy( remainingProps, predicate ); - useEffect( () => { if ( didMount.current ) { applyFromProps(); } - }, Object.values( prepareProps ) ); + }, dependencies ); useLayoutEffect( () => { applyRecord( record.current, { domOnly: true } ); @@ -1105,16 +1098,12 @@ function RichText( { applyRecord( record.current ); } - const ariaProps = pickBy( remainingProps, ( v, key ) => - startsWith( key, 'aria-' ) - ); - const editableProps = { // Overridable props. role: 'textbox', 'aria-multiline': true, 'aria-label': placeholder, - ...ariaProps, + ...remainingProps, ref, style: style ? { ...style, whiteSpace } : defaultStyle, className: classnames( 'rich-text', className ), @@ -1170,12 +1159,8 @@ function RichText( { ); } -const RichTextWrapper = withFormatTypes( RichText ); - /** * Renders a rich content input, providing users with the option to format the * content. */ -export default forwardRef( ( props, ref ) => { - return ; -} ); +export default forwardRef( RichText ); diff --git a/packages/rich-text/src/component/use-format-types.js b/packages/rich-text/src/component/use-format-types.js new file mode 100644 index 0000000000000..285e5ee540eaf --- /dev/null +++ b/packages/rich-text/src/component/use-format-types.js @@ -0,0 +1,102 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; + +function formatTypesSelector( select ) { + return select( 'core/rich-text' ).getFormatTypes(); +} + +/** + * This hook provides RichText with the `formatTypes` and its derived props from + * experimental format type settings. + * + * @param {Object} $0 Options + * @param {string} $0.clientId Block client ID. + * @param {string} $0.identifier Block attribute. + */ +export function useFormatTypes( { clientId, identifier } ) { + const formatTypes = useSelect( formatTypesSelector, [] ); + const keyedSelected = useSelect( + ( select ) => + formatTypes.reduce( ( accumulator, type ) => { + if ( type.__experimentalGetPropsForEditableTreePreparation ) { + accumulator[ + type.name + ] = type.__experimentalGetPropsForEditableTreePreparation( + select, + { + richTextIdentifier: identifier, + blockClientId: clientId, + } + ); + } + + return accumulator; + }, {} ), + [ formatTypes, clientId, identifier ] + ); + const dispatch = useDispatch(); + const prepareHandlers = []; + const valueHandlers = []; + const changeHandlers = []; + const dependencies = []; + + formatTypes.forEach( ( type ) => { + if ( type.__experimentalCreatePrepareEditableTree ) { + const selected = keyedSelected[ type.name ]; + const handler = type.__experimentalCreatePrepareEditableTree( + selected, + { + richTextIdentifier: identifier, + blockClientId: clientId, + } + ); + + if ( type.__experimentalCreateOnChangeEditableValue ) { + valueHandlers.push( handler ); + } else { + prepareHandlers.push( handler ); + } + + for ( const key in selected ) { + dependencies.push( selected[ key ] ); + } + } + + if ( type.__experimentalCreateOnChangeEditableValue ) { + let dispatchers = {}; + + if ( type.__experimentalGetPropsForEditableTreeChangeHandler ) { + dispatchers = type.__experimentalGetPropsForEditableTreeChangeHandler( + dispatch, + { + richTextIdentifier: identifier, + blockClientId: clientId, + } + ); + } + + changeHandlers.push( + type.__experimentalCreateOnChangeEditableValue( + { + ...( keyedSelected[ type.name ] || {} ), + ...dispatchers, + }, + { + richTextIdentifier: identifier, + blockClientId: clientId, + } + ) + ); + } + } ); + + return { + formatTypes, + prepareHandlers, + valueHandlers, + changeHandlers, + dependencies, + }; +} diff --git a/packages/rich-text/src/component/with-format-types.js b/packages/rich-text/src/component/with-format-types.js deleted file mode 100644 index 9a5c78a52609f..0000000000000 --- a/packages/rich-text/src/component/with-format-types.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * External dependencies - */ -import { mapKeys } from 'lodash'; - -/** - * WordPress dependencies - */ -import { useSelect, __unstableUseDispatchWithMap } from '@wordpress/data'; -import { useMemo } from '@wordpress/element'; - -function formatTypesSelector( select ) { - return select( 'core/rich-text' ).getFormatTypes(); -} - -/** - * This higher-order component provides RichText with the `formatTypes` prop - * and its derived props from experimental format type settings. - * - * @param {WPComponent} RichText The rich text component to add props for. - * - * @return {WPComponent} New enhanced component. - */ -export default function withFormatTypes( RichText ) { - return function WithFormatTypes( props ) { - const { clientId, identifier } = props; - const formatTypes = useSelect( formatTypesSelector, [] ); - const selectProps = useSelect( - ( select ) => { - return formatTypes.reduce( ( acc, settings ) => { - if ( - ! settings.__experimentalGetPropsForEditableTreePreparation - ) { - return acc; - } - - const selectPrefix = `format_prepare_props_(${ settings.name })_`; - return { - ...acc, - ...mapKeys( - settings.__experimentalGetPropsForEditableTreePreparation( - select, - { - richTextIdentifier: identifier, - blockClientId: clientId, - } - ), - ( value, key ) => selectPrefix + key - ), - }; - }, {} ); - }, - [ formatTypes, clientId, identifier ] - ); - const dispatchProps = __unstableUseDispatchWithMap( - ( dispatch ) => { - return formatTypes.reduce( ( acc, settings ) => { - if ( - ! settings.__experimentalGetPropsForEditableTreeChangeHandler - ) { - return acc; - } - - const dispatchPrefix = `format_on_change_props_(${ settings.name })_`; - return { - ...acc, - ...mapKeys( - settings.__experimentalGetPropsForEditableTreeChangeHandler( - dispatch, - { - richTextIdentifier: identifier, - blockClientId: clientId, - } - ), - ( value, key ) => dispatchPrefix + key - ), - }; - }, {} ); - }, - [ formatTypes, clientId, identifier ] - ); - const newProps = useMemo( () => { - return formatTypes.reduce( ( acc, settings ) => { - if ( ! settings.__experimentalCreatePrepareEditableTree ) { - return acc; - } - - const args = { - richTextIdentifier: identifier, - blockClientId: clientId, - }; - const combined = { - ...selectProps, - ...dispatchProps, - }; - - const { name } = settings; - const selectPrefix = `format_prepare_props_(${ name })_`; - const dispatchPrefix = `format_on_change_props_(${ name })_`; - const propsByPrefix = Object.keys( combined ).reduce( - ( accumulator, key ) => { - if ( key.startsWith( selectPrefix ) ) { - accumulator[ key.slice( selectPrefix.length ) ] = - combined[ key ]; - } - - if ( key.startsWith( dispatchPrefix ) ) { - accumulator[ key.slice( dispatchPrefix.length ) ] = - combined[ key ]; - } - - return accumulator; - }, - {} - ); - - if ( settings.__experimentalCreateOnChangeEditableValue ) { - return { - ...acc, - [ `format_value_functions_(${ name })` ]: settings.__experimentalCreatePrepareEditableTree( - propsByPrefix, - args - ), - [ `format_on_change_functions_(${ name })` ]: settings.__experimentalCreateOnChangeEditableValue( - propsByPrefix, - args - ), - }; - } - - return { - ...acc, - [ `format_prepare_functions_(${ name })` ]: settings.__experimentalCreatePrepareEditableTree( - propsByPrefix, - args - ), - }; - }, {} ); - }, [ formatTypes, clientId, identifier, selectProps, dispatchProps ] ); - - return ( - - ); - }; -} From 2d5106a894eb172eb63805b758275171ca1b483b Mon Sep 17 00:00:00 2001 From: Mitchell Austin Date: Mon, 28 Sep 2020 11:04:39 -0700 Subject: [PATCH 31/79] Fix: range control direct entry in input field (#25609) * add test for RangeControl to permit invalid entry in input field * have InputControl use focus state to inform value syncing * remove the wrapper RangeControl has around NumberControl * update tests with InputControl to focus input before change events * prevent RangeControl from calling onChange with invalid values Additionally: - Test updates - A formatting fix. * blur the active element when AnglePickerControl's dial is manipulated * mend logic for number input value resolution in RangeControl - on blur use handleOnReset if allowRest is true - call onChange with a clamped value if new value is out-of-range - update test to use invalid value instead of out-of-range value - aside: remove an extraneous prop from number input * fix and update readme --- .../src/angle-picker-control/angle-circle.js | 3 +- .../components/src/box-control/test/index.js | 16 +- .../components/src/input-control/index.js | 15 +- .../src/input-control/input-field.js | 38 +-- .../src/input-control/test/index.js | 18 +- .../src/number-control/test/index.js | 303 ++++++----------- .../components/src/range-control/README.md | 19 +- .../components/src/range-control/index.js | 39 ++- .../src/range-control/input-field.js | 86 ----- .../src/range-control/test/index.js | 50 ++- .../components/src/unit-control/test/index.js | 315 +++++++----------- 11 files changed, 327 insertions(+), 575 deletions(-) delete mode 100644 packages/components/src/range-control/input-field.js diff --git a/packages/components/src/angle-picker-control/angle-circle.js b/packages/components/src/angle-picker-control/angle-circle.js index 6122cda215daf..f2a761284d96e 100644 --- a/packages/components/src/angle-picker-control/angle-circle.js +++ b/packages/components/src/angle-picker-control/angle-circle.js @@ -31,7 +31,8 @@ function AngleCircle( { value, onChange, ...props } ) { // Prevent (drag) mouse events from selecting and accidentally // triggering actions from other elements. event.preventDefault(); - + // Ensure the input isn't focused as preventDefault would leave it + document.activeElement.blur(); onChange( getAngle( centerX, centerY, event.clientX, event.clientY ) ); }; diff --git a/packages/components/src/box-control/test/index.js b/packages/components/src/box-control/test/index.js index 613aac6092fe6..81b1ab7bd2bf6 100644 --- a/packages/components/src/box-control/test/index.js +++ b/packages/components/src/box-control/test/index.js @@ -28,6 +28,7 @@ describe( 'BoxControl', () => { const input = container.querySelector( 'input' ); const unitSelect = container.querySelector( 'select' ); + input.focus(); fireEvent.change( input, { target: { value: '100%' } } ); fireEvent.keyDown( input, { keyCode: ENTER } ); @@ -41,14 +42,17 @@ describe( 'BoxControl', () => { const { container, getByText } = render( ); const input = container.querySelector( 'input' ); const unitSelect = container.querySelector( 'select' ); + const reset = getByText( /Reset/ ); + input.focus(); fireEvent.change( input, { target: { value: '100px' } } ); fireEvent.keyDown( input, { keyCode: ENTER } ); expect( input.value ).toBe( '100' ); expect( unitSelect.value ).toBe( 'px' ); - fireEvent.click( getByText( /Reset/ ) ); + reset.focus(); + fireEvent.click( reset ); expect( input.value ).toBe( '' ); expect( unitSelect.value ).toBe( 'px' ); @@ -68,14 +72,17 @@ describe( 'BoxControl', () => { const { container, getByText } = render( ); const input = container.querySelector( 'input' ); const unitSelect = container.querySelector( 'select' ); + const reset = getByText( /Reset/ ); + input.focus(); fireEvent.change( input, { target: { value: '100px' } } ); fireEvent.keyDown( input, { keyCode: ENTER } ); expect( input.value ).toBe( '100' ); expect( unitSelect.value ).toBe( 'px' ); - fireEvent.click( getByText( /Reset/ ) ); + reset.focus(); + fireEvent.click( reset ); expect( input.value ).toBe( '' ); expect( unitSelect.value ).toBe( 'px' ); @@ -102,14 +109,17 @@ describe( 'BoxControl', () => { const { container, getByText } = render( ); const input = container.querySelector( 'input' ); const unitSelect = container.querySelector( 'select' ); + const reset = getByText( /Reset/ ); + input.focus(); fireEvent.change( input, { target: { value: '100px' } } ); fireEvent.keyDown( input, { keyCode: ENTER } ); expect( input.value ).toBe( '100' ); expect( unitSelect.value ).toBe( 'px' ); - fireEvent.click( getByText( /Reset/ ) ); + reset.focus(); + fireEvent.click( reset ); expect( input.value ).toBe( '' ); expect( unitSelect.value ).toBe( 'px' ); diff --git a/packages/components/src/input-control/index.js b/packages/components/src/input-control/index.js index bd6cb82d8be98..e56e86e8a384b 100644 --- a/packages/components/src/input-control/index.js +++ b/packages/components/src/input-control/index.js @@ -33,9 +33,7 @@ export function InputControl( isPressEnterToChange = false, label, labelPosition = 'top', - onBlur = noop, onChange = noop, - onFocus = noop, onValidate = noop, onKeyDown = noop, prefix, @@ -51,16 +49,6 @@ export function InputControl( const id = useUniqueId( idProp ); const classes = classNames( 'components-input-control', className ); - const handleOnBlur = ( event ) => { - onBlur( event ); - setIsFocused( false ); - }; - - const handleOnFocus = ( event ) => { - onFocus( event ); - setIsFocused( true ); - }; - return ( state, value: valueProp, ...props @@ -63,35 +66,27 @@ function InputField( const { _event, value, isDragging, isDirty } = state; - const valueRef = useRef( value ); const dragCursor = useDragCursor( isDragging, dragDirection ); - useEffect( () => { - /** - * Handles syncing incoming value changes with internal state. - * This effectively enables a "controlled" state. - * https://reactjs.org/docs/forms.html#controlled-components - */ - if ( valueProp !== valueRef.current ) { - update( valueProp ); - valueRef.current = valueProp; - - // Quick return to avoid firing the onChange callback + /* + * Syncs value state using the focus state to determine the direction. + * Without focus it updates the value from the props. With focus it + * propagates the value and event through onChange. + */ + useUpdateEffect( () => { + if ( valueProp === value ) { return; } - - /** - * Fires the onChange callback when internal state value changes. - */ - if ( value !== valueRef.current && ! isDirty ) { + if ( ! isFocused ) { + update( valueProp ); + } else if ( ! isDirty ) { onChange( value, { event: _event } ); - - valueRef.current = value; } - }, [ value, isDirty, valueProp ] ); + }, [ value, isDirty, isFocused, valueProp ] ); const handleOnBlur = ( event ) => { onBlur( event ); + setIsFocused( false ); /** * If isPressEnterToChange is set, this commits the value to @@ -108,6 +103,7 @@ function InputField( const handleOnFocus = ( event ) => { onFocus( event ); + setIsFocused( true ); }; const handleOnChange = ( event ) => { diff --git a/packages/components/src/input-control/test/index.js b/packages/components/src/input-control/test/index.js index 86615318497fc..4198a2308fd47 100644 --- a/packages/components/src/input-control/test/index.js +++ b/packages/components/src/input-control/test/index.js @@ -49,7 +49,7 @@ describe( 'InputControl', () => { render( ); const input = getInput(); - + input.focus(); fireEvent.change( input, { target: { value: 'There' } } ); expect( input.value ).toBe( 'There' ); @@ -59,21 +59,23 @@ describe( 'InputControl', () => { it( 'should work as a controlled component', () => { const spy = jest.fn(); const { rerender } = render( - + ); const input = getInput(); - fireEvent.change( input, { target: { value: 'State' } } ); + input.focus(); + fireEvent.change( input, { target: { value: 'two' } } ); - // Assuming is controlled... + // Ensuring is controlled + fireEvent.blur( input ); // Updating the value - rerender( ); + rerender( ); - expect( input.value ).toBe( 'New' ); + expect( input.value ).toBe( 'three' ); - /** + /* * onChange called only once. onChange is not called when a * parent component explicitly passed a (new value) change down to * the . @@ -89,7 +91,7 @@ describe( 'InputControl', () => { const input = getInput(); - // Assuming is controlled... + // Assuming is controlled (not focused) // Updating the value rerender( ); diff --git a/packages/components/src/number-control/test/index.js b/packages/components/src/number-control/test/index.js index 8c006647bd2ff..883b7f6d9591f 100644 --- a/packages/components/src/number-control/test/index.js +++ b/packages/components/src/number-control/test/index.js @@ -1,8 +1,7 @@ /** * External dependencies */ -import { render, unmountComponentAtNode } from 'react-dom'; -import { act, Simulate } from 'react-dom/test-utils'; +import { render, screen, fireEvent } from '@testing-library/react'; /** * WordPress dependencies @@ -13,24 +12,16 @@ import { UP, DOWN, ENTER } from '@wordpress/keycodes'; /** * Internal dependencies */ -import NumberControl from '../'; +import BaseNumberControl from '../'; -let container = null; +const getInput = () => screen.getByTestId( 'input' ); -function getInput() { - return container.querySelector( 'input' ); -} - -beforeEach( () => { - container = document.createElement( 'div' ); - document.body.appendChild( container ); -} ); +const fireKeyDown = ( data ) => + fireEvent.keyDown( document.activeElement || document.body, data ); -afterEach( () => { - unmountComponentAtNode( container ); - container.remove(); - container = null; -} ); +const NumberControl = ( props ) => ( + +); function StatefulNumberControl( props ) { const [ value, setValue ] = useState( props.value ); @@ -48,86 +39,56 @@ function StatefulNumberControl( props ) { describe( 'NumberControl', () => { describe( 'Basic rendering', () => { it( 'should render', () => { - act( () => { - render( , container ); - } ); - - const input = getInput(); - - expect( input ).not.toBeNull(); + render( ); + expect( getInput() ).not.toBeNull(); } ); it( 'should render custom className', () => { - act( () => { - render( , container ); - } ); - - const input = container.querySelector( '.hello' ); - - expect( input ).toBeTruthy(); + render( ); + expect( getInput() ).toBeTruthy(); } ); } ); describe( 'onChange handling', () => { it( 'should provide onChange callback with number value', () => { const spy = jest.fn(); - act( () => { - render( - , - container - ); - } ); - - const input = getInput(); - - input.value = 10; - act( () => { - Simulate.change( input ); - } ); + render( + spy( v ) } /> + ); - const changeValue = spy.mock.calls[ 0 ][ 0 ]; + const input = getInput(); + input.focus(); + fireEvent.change( input, { target: { value: 10 } } ); - expect( changeValue ).toBe( '10' ); + expect( spy ).toHaveBeenCalledWith( '10' ); } ); } ); describe( 'Validation', () => { it( 'should clamp value within range on ENTER keypress', () => { - act( () => { - render( - , - container - ); - } ); + render( ); const input = getInput(); - input.value = -100; - - act( () => { - Simulate.change( input ); - Simulate.keyDown( input, { keyCode: ENTER } ); - } ); + input.focus(); + fireEvent.change( input, { target: { value: -100 } } ); + fireKeyDown( { keyCode: ENTER } ); /** * This is zero because the value has been adjusted to * respect the min/max range of the input. */ + expect( input.value ).toBe( '0' ); } ); it( 'should parse to number value on ENTER keypress', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); - input.value = '10 abc'; - - act( () => { - Simulate.change( input ); - Simulate.keyDown( input, { keyCode: ENTER } ); - } ); + input.focus(); + fireEvent.change( input, { target: { value: '10 abc' } } ); + fireKeyDown( { keyCode: ENTER } ); expect( input.value ).toBe( '0' ); } ); @@ -136,122 +97,83 @@ describe( 'NumberControl', () => { describe( 'Key UP interactions', () => { it( 'should fire onKeyDown callback', () => { const spy = jest.fn(); - act( () => { - render( - , - container - ); - } ); - const input = getInput(); + render( ); - act( () => { - Simulate.keyDown( input, { keyCode: UP } ); - } ); + getInput().focus(); + fireKeyDown( { keyCode: UP } ); expect( spy ).toHaveBeenCalled(); } ); it( 'should increment by step on key UP press', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP } ); - } ); + input.focus(); + fireKeyDown( { keyCode: UP } ); expect( input.value ).toBe( '6' ); } ); it( 'should increment from a negative value', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP } ); - } ); + input.focus(); + fireKeyDown( { keyCode: UP } ); expect( input.value ).toBe( '-4' ); } ); it( 'should increment by shiftStep on key UP + shift press', () => { - act( () => { - render( - , - container - ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: UP, shiftKey: true } ); expect( input.value ).toBe( '20' ); } ); it( 'should increment by custom shiftStep on key UP + shift press', () => { - act( () => { - render( - , - container - ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: UP, shiftKey: true } ); expect( input.value ).toBe( '100' ); } ); it( 'should increment but be limited by max on shiftStep', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: UP, shiftKey: true } ); expect( input.value ).toBe( '99' ); } ); it( 'should not increment by shiftStep if disabled', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: UP, shiftKey: true } ); expect( input.value ).toBe( '6' ); } ); @@ -260,119 +182,82 @@ describe( 'NumberControl', () => { describe( 'Key DOWN interactions', () => { it( 'should fire onKeyDown callback', () => { const spy = jest.fn(); - act( () => { - render( - , - container - ); - } ); + render( ); - const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN } ); - } ); + getInput().focus(); + fireKeyDown( { keyCode: DOWN } ); expect( spy ).toHaveBeenCalled(); } ); it( 'should decrement by step on key DOWN press', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN } ); - } ); + input.focus(); + fireKeyDown( { keyCode: DOWN } ); expect( input.value ).toBe( '4' ); } ); it( 'should decrement from a negative value', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN } ); - } ); + input.focus(); + fireKeyDown( { keyCode: DOWN } ); expect( input.value ).toBe( '-6' ); } ); it( 'should decrement by shiftStep on key DOWN + shift press', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: DOWN, shiftKey: true } ); expect( input.value ).toBe( '0' ); } ); it( 'should decrement by custom shiftStep on key DOWN + shift press', () => { - act( () => { - render( - , - container - ); - } ); + render( ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: DOWN, shiftKey: true } ); expect( input.value ).toBe( '-100' ); } ); it( 'should decrement but be limited by min on shiftStep', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: DOWN, shiftKey: true } ); expect( input.value ).toBe( '4' ); } ); it( 'should not decrement by shiftStep if disabled', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); - } ); + input.focus(); + fireKeyDown( { keyCode: DOWN, shiftKey: true } ); expect( input.value ).toBe( '4' ); } ); diff --git a/packages/components/src/range-control/README.md b/packages/components/src/range-control/README.md index c42614583dc2e..f5e3853836982 100644 --- a/packages/components/src/range-control/README.md +++ b/packages/components/src/range-control/README.md @@ -213,8 +213,7 @@ const MyRangeControl() { #### onChange -A function that receives the new value. -If allowReset is true, when onChange is called without any parameter passed it should reset the value. +A function that receives the new value. The value will be less than `max` and more than `min` unless a reset (enabled by `allowReset`) has occured. In which case the value will be either that of `resetFallbackValue` if it has been specified or otherwise `undefined`. - Type: `function` - Required: Yes @@ -222,14 +221,22 @@ If allowReset is true, when onChange is called without any parameter passed it s #### min -The minimum value accepted. If smaller values are inserted onChange will not be called and the value gets reverted when blur event fires. +The minimum `value` allowed. - Type: `Number` - Required: No +- Default: 0 - Platform: Web | Mobile #### max +The maximum `value` allowed. + +- Type: `Number` +- Required: No +- Default: 100 +- Platform: Web | Mobile + #### railColor Customizes the (background) color of the rail element. @@ -238,12 +245,6 @@ Customizes the (background) color of the rail element. - Required: No - Platform: Web -The maximum value accepted. If higher values are inserted onChange will not be called and the value gets reverted when blur event fires. - -- Type: `Number` -- Required: No -- Platform: Web | Mobile - #### renderTooltipContent A way to customize the rendered UI of the value. Example: diff --git a/packages/components/src/range-control/index.js b/packages/components/src/range-control/index.js index 536b0d84ba0a8..90f55c3c0cd55 100644 --- a/packages/components/src/range-control/index.js +++ b/packages/components/src/range-control/index.js @@ -26,13 +26,13 @@ import { ActionRightWrapper, AfterIconWrapper, BeforeIconWrapper, + InputNumber, Root, Track, ThumbWrapper, Thumb, Wrapper, } from './styles/range-control-styles'; -import InputField from './input-field'; import { useRTL } from '../utils/rtl'; function RangeControl( @@ -77,6 +77,7 @@ function RangeControl( value: valueProp, initial: initialPosition, } ); + const isResetPendent = useRef( false ); const [ showTooltip, setShowTooltip ] = useState( showTooltipProp ); const [ isFocused, setIsFocused ] = useState( false ); @@ -119,17 +120,33 @@ function RangeControl( const handleOnRangeChange = ( event ) => { const nextValue = parseFloat( event.target.value ); - handleOnChange( nextValue ); + setValue( nextValue ); + onChange( nextValue ); }; const handleOnChange = ( nextValue ) => { - if ( isNaN( nextValue ) ) { - handleOnReset(); - return; + nextValue = parseFloat( nextValue ); + setValue( nextValue ); + /* + * Calls onChange only when nextValue is numeric + * otherwise may queue a reset for the blur event. + */ + if ( ! isNaN( nextValue ) ) { + if ( nextValue < min || nextValue > max ) { + nextValue = floatClamp( nextValue, min, max ); + } + onChange( nextValue ); + isResetPendent.current = false; + } else if ( allowReset ) { + isResetPendent.current = true; } + }; - setValue( nextValue ); - onChange( nextValue ); + const handleOnInputNumberBlur = () => { + if ( isResetPendent.current ) { + handleOnReset(); + isResetPendent.current = false; + } }; const handleOnReset = () => { @@ -256,14 +273,16 @@ function RangeControl( ) } { withInputField && ( - to be updated independently before the - * value is applied and propagated. This independent updating action is - * necessary to accommodate individual keystroke values that may not - * be considered "valid" (e.g. within the min - max range). - */ - const [ value, setValue ] = useControlledState( valueProp ); - - const handleOnReset = ( event ) => { - onReset( event ); - setValue( '' ); - }; - - const handleOnCommit = ( event ) => { - const nextValue = parseFloat( event.target.value ); - - if ( isNaN( nextValue ) ) { - handleOnReset(); - return; - } - - setValue( nextValue ); - onChange( nextValue ); - }; - - const handleOnBlur = ( event ) => { - onBlur( event ); - handleOnCommit( event ); - }; - - const handleOnChange = ( next ) => { - handleOnCommit( { target: { value: next } } ); - }; - - const handleOnKeyDown = ( event ) => { - const { keyCode } = event; - onKeyDown( event ); - - if ( keyCode === ENTER ) { - event.preventDefault(); - handleOnCommit( event ); - } - }; - - return ( - - ); -} diff --git a/packages/components/src/range-control/test/index.js b/packages/components/src/range-control/test/index.js index e3d75083f4e4f..2ccb8d90293fa 100644 --- a/packages/components/src/range-control/test/index.js +++ b/packages/components/src/range-control/test/index.js @@ -27,10 +27,11 @@ describe( 'RangeControl', () => { const rangeInput = getRangeInput( container ); const numberInput = getNumberInput( container ); + rangeInput.focus(); fireEvent.change( rangeInput, { target: { value: '5' } } ); + numberInput.focus(); fireEvent.change( numberInput, { target: { value: '10' } } ); - fireEvent.blur( numberInput ); expect( onChange ).toHaveBeenCalledWith( 5 ); expect( onChange ).toHaveBeenCalledWith( 10 ); @@ -57,7 +58,7 @@ describe( 'RangeControl', () => { } ); describe( 'validation', () => { - it( 'should not apply new value is lower than minimum', () => { + it( 'should not apply if new value is lower than minimum', () => { const { container } = render( ); const rangeInput = getRangeInput( container ); @@ -69,7 +70,7 @@ describe( 'RangeControl', () => { expect( rangeInput.value ).not.toBe( '10' ); } ); - it( 'should not apply new value is greater than maximum', () => { + it( 'should not apply if new value is greater than maximum', () => { const { container } = render( ); const rangeInput = getRangeInput( container ); @@ -81,20 +82,38 @@ describe( 'RangeControl', () => { expect( rangeInput.value ).not.toBe( '21' ); } ); - it( 'should call onChange if new value is valid', () => { + it( 'should not call onChange if new value is invalid', () => { const onChange = jest.fn(); const { container } = render( ); + const numberInput = getNumberInput( container ); + + numberInput.focus(); + fireEvent.change( numberInput, { target: { value: '25e' } } ); + + expect( onChange ).not.toHaveBeenCalled(); + } ); + + it( 'should keep invalid values in number input until loss of focus', () => { + const onChange = jest.fn(); + const { container } = render( + + ); + const rangeInput = getRangeInput( container ); const numberInput = getNumberInput( container ); - fireEvent.change( numberInput, { target: { value: '15' } } ); - fireEvent.blur( numberInput ); + numberInput.focus(); + fireEvent.change( numberInput, { target: { value: '-1.1' } } ); + + expect( numberInput.value ).toBe( '-1.1' ); + expect( rangeInput.value ).toBe( '-1' ); - expect( onChange ).toHaveBeenCalledWith( 15 ); - expect( rangeInput.value ).toBe( '15' ); + fireEvent.blur( numberInput ); + expect( onChange ).toHaveBeenCalledWith( -1 ); + expect( numberInput.value ).toBe( '-1' ); } ); it( 'should validate when provided a max or min of zero', () => { @@ -105,6 +124,7 @@ describe( 'RangeControl', () => { const rangeInput = getRangeInput( container ); const numberInput = getNumberInput( container ); + numberInput.focus(); fireEvent.change( numberInput, { target: { value: '1' } } ); fireEvent.blur( numberInput ); @@ -119,19 +139,15 @@ describe( 'RangeControl', () => { const rangeInput = getRangeInput( container ); const numberInput = getNumberInput( container ); - fireEvent.change( numberInput, { target: { value: '-101' } } ); - fireEvent.blur( numberInput ); + numberInput.focus(); + fireEvent.change( numberInput, { target: { value: '-101' } } ); expect( rangeInput.value ).toBe( '-100' ); fireEvent.change( numberInput, { target: { value: '-49' } } ); - fireEvent.blur( numberInput ); - expect( rangeInput.value ).toBe( '-50' ); fireEvent.change( numberInput, { target: { value: '-50' } } ); - fireEvent.blur( numberInput ); - expect( rangeInput.value ).toBe( '-50' ); } ); @@ -148,14 +164,13 @@ describe( 'RangeControl', () => { const rangeInput = getRangeInput( container ); const numberInput = getNumberInput( container ); + numberInput.focus(); fireEvent.change( numberInput, { target: { value: '0.125' } } ); - fireEvent.blur( numberInput ); expect( onChange ).toHaveBeenCalledWith( 0.125 ); expect( rangeInput.value ).toBe( '0.125' ); fireEvent.change( numberInput, { target: { value: '0.225' } } ); - fireEvent.blur( numberInput ); expect( onChange ).toHaveBeenCalledWith( 0.225 ); expect( rangeInput.value ).toBe( '0.225' ); @@ -229,13 +244,14 @@ describe( 'RangeControl', () => { const rangeInput = getRangeInput( container ); const numberInput = getNumberInput( container ); + rangeInput.focus(); fireEvent.change( rangeInput, { target: { value: 13 } } ); expect( rangeInput.value ).toBe( '13' ); expect( numberInput.value ).toBe( '13' ); + numberInput.focus(); fireEvent.change( numberInput, { target: { value: 7 } } ); - fireEvent.blur( numberInput ); expect( rangeInput.value ).toBe( '7' ); expect( numberInput.value ).toBe( '7' ); diff --git a/packages/components/src/unit-control/test/index.js b/packages/components/src/unit-control/test/index.js index 8eac58f0dfdba..21d102a6f97e3 100644 --- a/packages/components/src/unit-control/test/index.js +++ b/packages/components/src/unit-control/test/index.js @@ -1,46 +1,32 @@ /** * External dependencies */ -import { render, unmountComponentAtNode } from 'react-dom'; -import { act, Simulate } from 'react-dom/test-utils'; -import { render as testRender } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; /** * WordPress dependencies */ -import { UP, DOWN } from '@wordpress/keycodes'; +import { UP, DOWN, ENTER } from '@wordpress/keycodes'; /** * Internal dependencies */ import UnitControl from '../'; -let container = null; - -beforeEach( () => { - container = document.createElement( 'div' ); - document.body.appendChild( container ); -} ); - -afterEach( () => { - unmountComponentAtNode( container ); - container.remove(); - container = null; -} ); - const getComponent = () => - container.querySelector( '.components-unit-control' ); + document.body.querySelector( '.components-unit-control' ); const getInput = () => - container.querySelector( '.components-unit-control input' ); + document.body.querySelector( '.components-unit-control input' ); const getSelect = () => - container.querySelector( '.components-unit-control select' ); + document.body.querySelector( '.components-unit-control select' ); + +const fireKeyDown = ( data ) => + fireEvent.keyDown( document.activeElement || document.body, data ); describe( 'UnitControl', () => { describe( 'Basic rendering', () => { it( 'should render', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); const select = getSelect(); @@ -49,9 +35,7 @@ describe( 'UnitControl', () => { } ); it( 'should render custom className', () => { - act( () => { - render( , container ); - } ); + render( ); const el = getComponent(); @@ -59,9 +43,7 @@ describe( 'UnitControl', () => { } ); it( 'should not render select, if units are disabled', () => { - act( () => { - render( , container ); - } ); + render( ); const input = getInput(); const select = getSelect(); @@ -72,61 +54,39 @@ describe( 'UnitControl', () => { describe( 'Value', () => { it( 'should update value on change', () => { - let state = 50; - const setState = ( nextState ) => ( state = nextState ); + let state = '50px'; + const setState = jest.fn( ( value ) => ( state = value ) ); - act( () => { - render( - , - container - ); - } ); + render( ); const input = getInput(); + input.focus(); + fireEvent.change( input, { target: { value: 62 } } ); - act( () => { - Simulate.change( input, { target: { value: 62 } } ); - } ); - + expect( setState ).toHaveBeenCalledTimes( 1 ); expect( state ).toBe( '62px' ); } ); it( 'should increment value on UP press', () => { - let state = 50; + let state = '50px'; const setState = ( nextState ) => ( state = nextState ); - act( () => { - render( - , - container - ); - } ); - - const input = getInput(); + render( ); - act( () => { - Simulate.keyDown( input, { keyCode: UP } ); - } ); + getInput().focus(); + fireKeyDown( { keyCode: UP } ); expect( state ).toBe( '51px' ); } ); it( 'should increment value on UP + SHIFT press, with step', () => { - let state = 50; + let state = '50px'; const setState = ( nextState ) => ( state = nextState ); - act( () => { - render( - , - container - ); - } ); + render( ); - const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); - } ); + getInput().focus(); + fireKeyDown( { keyCode: UP, shiftKey: true } ); expect( state ).toBe( '60px' ); } ); @@ -135,18 +95,10 @@ describe( 'UnitControl', () => { let state = 50; const setState = ( nextState ) => ( state = nextState ); - act( () => { - render( - , - container - ); - } ); - - const input = getInput(); + render( ); - act( () => { - Simulate.keyDown( input, { keyCode: DOWN } ); - } ); + getInput().focus(); + fireKeyDown( { keyCode: DOWN } ); expect( state ).toBe( '49px' ); } ); @@ -155,18 +107,10 @@ describe( 'UnitControl', () => { let state = 50; const setState = ( nextState ) => ( state = nextState ); - act( () => { - render( - , - container - ); - } ); + render( ); - const input = getInput(); - - act( () => { - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); - } ); + getInput().focus(); + fireKeyDown( { keyCode: DOWN, shiftKey: true } ); expect( state ).toBe( '40px' ); } ); @@ -177,18 +121,11 @@ describe( 'UnitControl', () => { let state = 'px'; const setState = ( nextState ) => ( state = nextState ); - act( () => { - render( - , - container - ); - } ); + render( ); const select = getSelect(); - - act( () => { - Simulate.change( select, { target: { value: 'em' } } ); - } ); + select.focus(); + fireEvent.change( select, { target: { value: 'em' } } ); expect( state ).toBe( 'em' ); } ); @@ -198,9 +135,8 @@ describe( 'UnitControl', () => { { value: 'pt', label: 'pt', default: 0 }, { value: 'vmax', label: 'vmax', default: 10 }, ]; - act( () => { - render( , container ); - } ); + + render( ); const select = getSelect(); const options = select.querySelectorAll( 'option' ); @@ -221,29 +157,24 @@ describe( 'UnitControl', () => { { value: 'pt', label: 'pt', default: 25 }, { value: 'vmax', label: 'vmax', default: 75 }, ]; - act( () => { - render( - , - container - ); - } ); + + render( + + ); const select = getSelect(); + select.focus(); - act( () => { - Simulate.change( select, { target: { value: 'vmax' } } ); - } ); + fireEvent.change( select, { target: { value: 'vmax' } } ); expect( state ).toBe( '75vmax' ); - act( () => { - Simulate.change( select, { target: { value: 'pt' } } ); - } ); + fireEvent.change( select, { target: { value: 'pt' } } ); expect( state ).toBe( '25pt' ); } ); @@ -256,146 +187,136 @@ describe( 'UnitControl', () => { { value: 'pt', label: 'pt', default: 25 }, { value: 'vmax', label: 'vmax', default: 75 }, ]; - act( () => { - render( - , - container - ); - } ); + + render( + + ); const select = getSelect(); + select.focus(); - act( () => { - Simulate.change( select, { target: { value: 'vmax' } } ); - } ); + fireEvent.change( select, { target: { value: 'vmax' } } ); expect( state ).toBe( '50vmax' ); - act( () => { - Simulate.change( select, { target: { value: 'pt' } } ); - } ); + fireEvent.change( select, { target: { value: 'pt' } } ); expect( state ).toBe( '50pt' ); } ); } ); + describe( 'Unit Parser', () => { let state = '10px'; - const setState = ( nextState ) => ( state = nextState ); + const setState = jest.fn( ( nextState ) => ( state = nextState ) ); it( 'should parse unit from input', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.change( input, { target: { value: '55 em' } } ); - } ); + input.focus(); + fireEvent.change( input, { target: { value: '55 em' } } ); + fireKeyDown( { keyCode: ENTER } ); expect( state ).toBe( '55em' ); } ); it( 'should parse PX unit from input', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.change( input, { target: { value: '61 PX' } } ); - } ); + input.focus(); + fireEvent.change( input, { target: { value: '61 PX' } } ); + fireKeyDown( { keyCode: ENTER } ); expect( state ).toBe( '61px' ); } ); it( 'should parse EM unit from input', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.change( input, { target: { value: '55 em' } } ); - } ); + input.focus(); + fireEvent.change( input, { target: { value: '55 em' } } ); + fireKeyDown( { keyCode: ENTER } ); expect( state ).toBe( '55em' ); } ); it( 'should parse % unit from input', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.change( input, { target: { value: '-10 %' } } ); - } ); + input.focus(); + fireEvent.change( input, { target: { value: '-10 %' } } ); + fireKeyDown( { keyCode: ENTER } ); expect( state ).toBe( '-10%' ); } ); it( 'should parse REM unit from input', () => { - act( () => { - render( - , - container - ); - } ); + render( + + ); const input = getInput(); - - act( () => { - Simulate.change( input, { - target: { value: '123 rEm ' }, - } ); + input.focus(); + fireEvent.change( input, { + target: { value: '123 rEm ' }, } ); + fireKeyDown( { keyCode: ENTER } ); expect( state ).toBe( '123rem' ); } ); it( 'should update unit after initial render and with new unit prop', () => { - const { container: testContainer, rerender } = testRender( - - ); + const { rerender } = render( ); - const select = testContainer.querySelector( 'select' ); + const select = getSelect(); expect( select.value ).toBe( '%' ); - rerender( ); + rerender( ); expect( select.value ).toBe( 'em' ); } ); it( 'should fallback to default unit if parsed unit is invalid', () => { - const { container: testContainer } = testRender( - - ); - - const select = testContainer.querySelector( 'select' ); + render( ); - expect( select.value ).toBe( 'px' ); + expect( getSelect().value ).toBe( 'px' ); } ); } ); } ); From 3f2c0e31a87887a938e174a9546aba5190b88c50 Mon Sep 17 00:00:00 2001 From: roo2 Date: Tue, 29 Sep 2020 05:40:45 +1000 Subject: [PATCH 32/79] wp-env: Add docs for inspecting the docker compose file (#25666) --- packages/env/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/env/README.md b/packages/env/README.md index 28c61f1bb6d40..7daa813fd9255 100644 --- a/packages/env/README.md +++ b/packages/env/README.md @@ -169,6 +169,25 @@ $ wp-env destroy $ wp-env start ``` +### 7. Debug mode and inspecting the generated dockerfile. + +`wp-env` uses docker behind the scenes. Inspecting the generated docker-compose file can help to understand what's going on. + +Start `wp-env` in debug mode + +```sh +wp-env start --debug +``` + +`wp-env` will output its config which includes `dockerComposeConfigPath`. + +```sh +ℹ Config: + ... + "dockerComposeConfigPath": "/Users/$USERNAME/.wp-env/5a619d332a92377cd89feb339c67b833/docker-compose.yml", + ... +``` + ## Command reference `wp-env` creates generated files in the `wp-env` home directory. By default, this is `~/.wp-env`. The exception is Linux, where files are placed at `~/wp-env` [for compatibility with Snap Packages](https://github.com/WordPress/gutenberg/issues/20180#issuecomment-587046325). The `wp-env` home directory contains a subdirectory for each project named `/$md5_of_project_path`. To change the `wp-env` home directory, set the `WP_ENV_HOME` environment variable. For example, running `WP_ENV_HOME="something" wp-env start` will download the project files to the directory `./something/$md5_of_project_path` (relative to the current directory). From ed848a0ac49257c1c8162fef09440a36c48a8375 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 28 Sep 2020 17:16:43 -0400 Subject: [PATCH 33/79] Fix image block render in Xcode 12 (#25470) * Fix image block render in Xcode 12 This PR fixes #25431 which is a bug that prevents Image blocks from rendering their images. This only affects builds made using Xcode 12. The bug itself is in React Native itself and was fixed in RN 0.63. Since we're on RN 0.61.5, we need to apply a patch (as suggested here https://github.com/facebook/react-native/issues/29279#issuecomment-657201709). The patch was applied using the following steps found here: https://github.com/facebook/react-native/issues/29279#issuecomment-657201709 * Remove unnecessary changes to RN patch file --- patches/react-native+0.61.5.patch | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/patches/react-native+0.61.5.patch b/patches/react-native+0.61.5.patch index b3c8c8ff3d257..72f668835cf2d 100644 --- a/patches/react-native+0.61.5.patch +++ b/patches/react-native+0.61.5.patch @@ -21,6 +21,20 @@ index 65fa2bb..c5f265b 100644 index++; } +diff --git a/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m b/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m +index 01aa75f..572572c 100644 +--- a/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m ++++ b/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m +@@ -266,6 +266,9 @@ - (void)displayDidRefresh:(CADisplayLink *)displayLink + + - (void)displayLayer:(CALayer *)layer + { ++ if (!_currentFrame) { ++ _currentFrame = self.image; ++ } + if (_currentFrame) { + layer.contentsScale = self.animatedImageScale; + layer.contents = (__bridge id)_currentFrame.CGImage; diff --git a/node_modules/react-native/React/Views/RCTShadowView.m b/node_modules/react-native/React/Views/RCTShadowView.m index 40c0cda..646f137 100644 --- a/node_modules/react-native/React/Views/RCTShadowView.m From b9ff7ea0adf17dcc188055726dc4cf9354b0b669 Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Mon, 28 Sep 2020 15:41:04 -0700 Subject: [PATCH 34/79] RN: Add Insert Button Mobile Component. (#25204) * RN: Add Icon Button Block Editor component. * Add the missing styles * Add readme descriptions Co-authored-by: Gerardo Pacheco --- .../block-variation-picker/index.native.js | 4 +- .../src/components/inserter/menu.native.js | 13 ++-- .../src/components/inserter/style.native.scss | 64 ------------------- packages/components/src/index.native.js | 2 +- .../src/mobile/inserter-button/README.md | 55 ++++++++++++++++ .../mobile/inserter-button/index.native.js} | 16 +++-- .../mobile/inserter-button/style.native.scss | 64 +++++++++++++++++++ 7 files changed, 141 insertions(+), 77 deletions(-) create mode 100644 packages/components/src/mobile/inserter-button/README.md rename packages/{block-editor/src/components/inserter/menu-item.native.js => components/src/mobile/inserter-button/index.native.js} (83%) create mode 100644 packages/components/src/mobile/inserter-button/style.native.scss diff --git a/packages/block-editor/src/components/block-variation-picker/index.native.js b/packages/block-editor/src/components/block-variation-picker/index.native.js index 0884b5aebdaed..9ad927c0b4796 100644 --- a/packages/block-editor/src/components/block-variation-picker/index.native.js +++ b/packages/block-editor/src/components/block-variation-picker/index.native.js @@ -21,6 +21,7 @@ import { PanelBody, BottomSheet, FooterMessageControl, + InserterButton, } from '@wordpress/components'; import { Icon, close } from '@wordpress/icons'; import { useMemo } from '@wordpress/element'; @@ -28,7 +29,6 @@ import { useMemo } from '@wordpress/element'; /** * Internal dependencies */ -import MenuItem from '../inserter/menu-item'; import styles from './style.scss'; const hitSlop = { top: 22, bottom: 22, left: 22, right: 22 }; @@ -103,7 +103,7 @@ function BlockVariationPicker( { isVisible, onClose, clientId, variations } ) { > { variations.map( ( v ) => { return ( - onVariationSelect( v ) } diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js index 76b1557ddfd68..c05ae07048b67 100644 --- a/packages/block-editor/src/components/inserter/menu.native.js +++ b/packages/block-editor/src/components/inserter/menu.native.js @@ -17,13 +17,16 @@ import { Component } from '@wordpress/element'; import { createBlock, rawHandler } from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; import { withInstanceId, compose } from '@wordpress/compose'; -import { BottomSheet, BottomSheetConsumer } from '@wordpress/components'; +import { + BottomSheet, + BottomSheetConsumer, + InserterButton, +} from '@wordpress/components'; /** * Internal dependencies */ import styles from './style.scss'; -import MenuItem from './menu-item.native'; const MIN_COL_NUM = 3; @@ -62,8 +65,8 @@ export class InserterMenu extends Component { const { paddingLeft: itemPaddingLeft, paddingRight: itemPaddingRight, - } = styles.modalItem; - const { width: itemWidth } = styles.modalIconWrapper; + } = InserterButton.Styles.modalItem; + const { width: itemWidth } = InserterButton.Styles.modalIconWrapper; return itemWidth + itemPaddingLeft + itemPaddingRight; } @@ -112,7 +115,7 @@ export class InserterMenu extends Component { const { itemWidth, maxWidth } = this.state; const { onSelect } = this.props; return ( - + Some rendered content here + } } onSelect={ function( item ) { console.log( 'selected' ); } } /> + + ); +} +``` + +_Note:_ + +## Props + +### `maxWidth` +* **Type:** `String` +* **Default:** `undefined` + +The max-width of the button. + +### `itemWidth` +* **Type:** `String` +* **Default:** `undefined` + +The button width. + +### `onSelect` +* **Type:** `Function` +* **Required** `true` + +The function that is called once the InserterButton has been selected. + +### `item` +* **Type:** `Object` +* **Required** `true` + +The object that gets selected. + +## Examples + + diff --git a/packages/block-editor/src/components/inserter/menu-item.native.js b/packages/components/src/mobile/inserter-button/index.native.js similarity index 83% rename from packages/block-editor/src/components/inserter/menu-item.native.js rename to packages/components/src/mobile/inserter-button/index.native.js index be47452eae1fb..2712b7b6a198f 100644 --- a/packages/block-editor/src/components/inserter/menu-item.native.js +++ b/packages/components/src/mobile/inserter-button/index.native.js @@ -55,13 +55,14 @@ class MenuItem extends Component { ); const isClipboardBlock = item.id === 'clipboard'; + const blockTitle = isClipboardBlock ? __( 'Copied block' ) : item.title; return ( @@ -82,13 +83,18 @@ class MenuItem extends Component { /> - - { isClipboardBlock ? __( 'Copied block' ) : item.title } - + { blockTitle } ); } } -export default withPreferredColorScheme( MenuItem ); +const InserterButton = withPreferredColorScheme( MenuItem ); + +InserterButton.Styles = { + modalItem: styles.modalItem, + modalIconWrapper: styles.modalIconWrapper, +}; + +export default InserterButton; diff --git a/packages/components/src/mobile/inserter-button/style.native.scss b/packages/components/src/mobile/inserter-button/style.native.scss new file mode 100644 index 0000000000000..1a8644ad78d8a --- /dev/null +++ b/packages/components/src/mobile/inserter-button/style.native.scss @@ -0,0 +1,64 @@ +/** @format */ +.touchableArea { + border-radius: 8px 8px 8px 8px; +} + +.modalIconWrapper { + width: 104px; + height: 64px; + background-color: $gray-light; //#f3f6f8 + border-radius: 8px 8px 8px 8px; + justify-content: center; + align-items: center; +} + +.modalIconWrapperDark { + background-color: rgba($white, 0.07); +} + +.modalIcon { + width: 32px; + height: 32px; + justify-content: center; + align-items: center; + fill: $gray-dark; +} + +.modalIconDark { + fill: $white; +} + +.modalItemLabel { + background-color: transparent; + padding-left: 2; + padding-right: 2; + padding-top: 4; + padding-bottom: 0; + justify-content: center; + font-size: 12; + color: $gray-dark; +} + +.modalItemLabelDark { + color: $white; +} + +.clipboardBlock { + background-color: transparent; + border-width: 1px; + border-color: $light-gray-400; +} + +.clipboardBlockDark { + border-color: $gray-70; +} + +.modalItem { + flex-direction: column; + justify-content: flex-start; + align-items: center; + padding-left: $grid-unit-20 / 2; + padding-right: $grid-unit-20 / 2; + padding-top: 0; + padding-bottom: 0; +} From f7b9201fbd4d7ddea0963f71db099a2fe5cda406 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Tue, 29 Sep 2020 11:48:14 +1000 Subject: [PATCH 35/79] Add move markers to list view. (#25205) * Add move markers to list view. * Select block before moving. --- .../src/components/block-actions/index.js | 2 ++ .../block-navigation/block-contents.js | 27 ++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-actions/index.js b/packages/block-editor/src/components/block-actions/index.js index 9f5e6593eb3e3..e7de0fa5c40f3 100644 --- a/packages/block-editor/src/components/block-actions/index.js +++ b/packages/block-editor/src/components/block-actions/index.js @@ -54,6 +54,7 @@ export default function BlockActions( { flashBlock, setBlockMovingClientId, setNavigationMode, + selectBlock, } = useDispatch( 'core/block-editor' ); const notifyCopy = useNotifyCopy(); @@ -78,6 +79,7 @@ export default function BlockActions( { }, onMoveTo() { setNavigationMode( true ); + selectBlock( clientIds[ 0 ] ); setBlockMovingClientId( clientIds[ 0 ] ); }, onGroup() { diff --git a/packages/block-editor/src/components/block-navigation/block-contents.js b/packages/block-editor/src/components/block-navigation/block-contents.js index 8bf5baea7985b..dc215cd461b18 100644 --- a/packages/block-editor/src/components/block-navigation/block-contents.js +++ b/packages/block-editor/src/components/block-navigation/block-contents.js @@ -37,14 +37,29 @@ const BlockNavigationBlockContents = forwardRef( const { clientId } = block; - const rootClientId = useSelect( - ( select ) => - select( 'core/block-editor' ).getBlockRootClientId( - clientId - ) || '', + const { + rootClientId, + blockMovingClientId, + selectedBlockInBlockEditor, + } = useSelect( + ( select ) => { + const { + getBlockRootClientId, + hasBlockMovingClientId, + getSelectedBlockClientId, + } = select( 'core/block-editor' ); + return { + rootClientId: getBlockRootClientId( clientId ) || '', + blockMovingClientId: hasBlockMovingClientId(), + selectedBlockInBlockEditor: getSelectedBlockClientId(), + }; + }, [ clientId ] ); + const isBlockMoveTarget = + blockMovingClientId && selectedBlockInBlockEditor === clientId; + const { rootClientId: dropTargetRootClientId, clientId: dropTargetClientId, @@ -65,7 +80,7 @@ const BlockNavigationBlockContents = forwardRef( const className = classnames( 'block-editor-block-navigation-block-contents', { - 'is-dropping-before': isDroppingBefore, + 'is-dropping-before': isDroppingBefore || isBlockMoveTarget, 'is-dropping-after': isDroppingAfter, 'is-dropping-to-inner-blocks': isDroppingToInnerBlocks, } From 96577a54069197a12296d2bac0307bbb1a512df7 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 29 Sep 2020 08:45:44 +0100 Subject: [PATCH 36/79] Bump plugin version to 9.1.0-rc.1 --- changelog.txt | 5 +++++ gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- readme.txt | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index 3ac19d9866268..07d7b4cd0214a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,10 @@ == Changelog == += 9.1.0-rc.1 = + +TODO: Changelog will be provided later. + + = 9.0.0 = ### Features diff --git a/gutenberg.php b/gutenberg.php index 2cda8621d0e06..54e972091ed19 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.3 * Requires PHP: 5.6 - * Version: 9.0.0 + * Version: 9.1.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 3b882d659ec8b..cd429d019d883 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "9.0.0", + "version": "9.1.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a220aefdb0a7b..9e1fadc551237 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "9.0.0", + "version": "9.1.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", diff --git a/readme.txt b/readme.txt index 2c80db360fe87..7d664a8b98a05 100644 --- a/readme.txt +++ b/readme.txt @@ -57,4 +57,4 @@ View release page. +To read the changelog for Gutenberg 9.1.0-rc.1, please navigate to the release page. From 29255c42cc531ea9739b9d744e4279856c5e7d84 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 29 Sep 2020 16:12:59 +0800 Subject: [PATCH 37/79] Move widget-area to edit-widgets (#25673) * Move widget-area to edit-widgets * Update blocks.php * Move css * Delete e2e tests --- lib/blocks.php | 3 ++- packages/block-library/src/editor.scss | 1 - packages/block-library/src/index.js | 2 -- .../fixtures/blocks/core__widget-area.html | 1 - .../fixtures/blocks/core__widget-area.json | 10 ---------- .../blocks/core__widget-area.parsed.json | 18 ------------------ .../blocks/core__widget-area.serialized.html | 1 - .../src/blocks}/widget-area/block.json | 0 .../src/blocks}/widget-area/edit/index.js | 0 .../blocks}/widget-area/edit/inner-blocks.js | 0 .../src/blocks}/widget-area/editor.scss | 0 .../src/blocks}/widget-area/index.js | 0 packages/edit-widgets/src/index.js | 2 ++ packages/edit-widgets/src/style.scss | 1 + 14 files changed, 5 insertions(+), 34 deletions(-) delete mode 100644 packages/e2e-tests/fixtures/blocks/core__widget-area.html delete mode 100644 packages/e2e-tests/fixtures/blocks/core__widget-area.json delete mode 100644 packages/e2e-tests/fixtures/blocks/core__widget-area.parsed.json delete mode 100644 packages/e2e-tests/fixtures/blocks/core__widget-area.serialized.html rename packages/{block-library/src => edit-widgets/src/blocks}/widget-area/block.json (100%) rename packages/{block-library/src => edit-widgets/src/blocks}/widget-area/edit/index.js (100%) rename packages/{block-library/src => edit-widgets/src/blocks}/widget-area/edit/inner-blocks.js (100%) rename packages/{block-library/src => edit-widgets/src/blocks}/widget-area/editor.scss (100%) rename packages/{block-library/src => edit-widgets/src/blocks}/widget-area/index.js (100%) diff --git a/lib/blocks.php b/lib/blocks.php index 0aa7c62e725b9..a35f6a659bd29 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -45,7 +45,6 @@ function gutenberg_reregister_core_block_types() { 'text-columns', 'verse', 'video', - 'widget-area', ), 'block_names' => array_merge( array( @@ -96,9 +95,11 @@ function gutenberg_reregister_core_block_types() { dirname( __FILE__ ) . '/../build/edit-widgets/blocks/' => array( 'block_folders' => array( 'legacy-widget', + 'widget-area', ), 'block_names' => array( 'legacy-widget.php' => 'core/legacy-widget', + 'widget-area.php' => 'core/widget-area', ), ), ); diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 2d4b530ce0a11..ee185fde8bdc9 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -49,7 +49,6 @@ @import "./text-columns/editor.scss"; @import "./verse/editor.scss"; @import "./video/editor.scss"; -@import "./widget-area/editor.scss"; @import "./query-loop/editor.scss"; @import "./query/editor.scss"; diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index d92cde6ca38d5..e99a917ec839b 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -61,7 +61,6 @@ import * as tagCloud from './tag-cloud'; import * as classic from './classic'; import * as socialLinks from './social-links'; import * as socialLink from './social-link'; -import * as widgetArea from './widget-area'; // Full Site Editing Blocks import * as siteLogo from './site-logo'; @@ -191,7 +190,6 @@ export const __experimentalRegisterExperimentalCoreBlocks = const { __experimentalEnableFullSiteEditing } = settings; [ - widgetArea, navigation, navigationLink, diff --git a/packages/e2e-tests/fixtures/blocks/core__widget-area.html b/packages/e2e-tests/fixtures/blocks/core__widget-area.html deleted file mode 100644 index 3d2e16369b819..0000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__widget-area.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/e2e-tests/fixtures/blocks/core__widget-area.json b/packages/e2e-tests/fixtures/blocks/core__widget-area.json deleted file mode 100644 index 578fdf0f1ab75..0000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__widget-area.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "clientId": "_clientId_0", - "name": "core/widget-area", - "isValid": true, - "attributes": {}, - "innerBlocks": [], - "originalContent": "" - } -] diff --git a/packages/e2e-tests/fixtures/blocks/core__widget-area.parsed.json b/packages/e2e-tests/fixtures/blocks/core__widget-area.parsed.json deleted file mode 100644 index 930bf95031a0f..0000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__widget-area.parsed.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "blockName": "core/widget-area", - "attrs": {}, - "innerBlocks": [], - "innerHTML": "", - "innerContent": [] - }, - { - "blockName": null, - "attrs": {}, - "innerBlocks": [], - "innerHTML": "\n", - "innerContent": [ - "\n" - ] - } -] diff --git a/packages/e2e-tests/fixtures/blocks/core__widget-area.serialized.html b/packages/e2e-tests/fixtures/blocks/core__widget-area.serialized.html deleted file mode 100644 index 3d2e16369b819..0000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__widget-area.serialized.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/block-library/src/widget-area/block.json b/packages/edit-widgets/src/blocks/widget-area/block.json similarity index 100% rename from packages/block-library/src/widget-area/block.json rename to packages/edit-widgets/src/blocks/widget-area/block.json diff --git a/packages/block-library/src/widget-area/edit/index.js b/packages/edit-widgets/src/blocks/widget-area/edit/index.js similarity index 100% rename from packages/block-library/src/widget-area/edit/index.js rename to packages/edit-widgets/src/blocks/widget-area/edit/index.js diff --git a/packages/block-library/src/widget-area/edit/inner-blocks.js b/packages/edit-widgets/src/blocks/widget-area/edit/inner-blocks.js similarity index 100% rename from packages/block-library/src/widget-area/edit/inner-blocks.js rename to packages/edit-widgets/src/blocks/widget-area/edit/inner-blocks.js diff --git a/packages/block-library/src/widget-area/editor.scss b/packages/edit-widgets/src/blocks/widget-area/editor.scss similarity index 100% rename from packages/block-library/src/widget-area/editor.scss rename to packages/edit-widgets/src/blocks/widget-area/editor.scss diff --git a/packages/block-library/src/widget-area/index.js b/packages/edit-widgets/src/blocks/widget-area/index.js similarity index 100% rename from packages/block-library/src/widget-area/index.js rename to packages/edit-widgets/src/blocks/widget-area/index.js diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index c3d5b063c86af..b4a958d0fb104 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -18,6 +18,7 @@ import { import './store'; import './hooks'; import { create as createLegacyWidget } from './blocks/legacy-widget'; +import * as widgetArea from './blocks/widget-area'; import EditWidgetsInitializer from './components/edit-widgets-initializer'; /** @@ -31,6 +32,7 @@ export function initialize( id, settings ) { if ( process.env.GUTENBERG_PHASE === 2 ) { __experimentalRegisterExperimentalCoreBlocks( settings ); registerBlock( createLegacyWidget( settings ) ); + registerBlock( widgetArea ); } render( , diff --git a/packages/edit-widgets/src/style.scss b/packages/edit-widgets/src/style.scss index 0b1af92ed323a..2454b1e335675 100644 --- a/packages/edit-widgets/src/style.scss +++ b/packages/edit-widgets/src/style.scss @@ -1,6 +1,7 @@ @import "../../interface/src/style.scss"; @import "./blocks/legacy-widget/editor.scss"; +@import "./blocks/widget-area/editor.scss"; @import "./components/header/style.scss"; @import "./components/sidebar/style.scss"; @import "./components/notices/style.scss"; From b3a6f4db276e564a47f2c9ed32867ec1afae6ca2 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 29 Sep 2020 16:13:26 +0800 Subject: [PATCH 38/79] Use h3 in the legacy widget title (#25690) --- .../edit-widgets/src/blocks/legacy-widget/edit/handler.js | 4 ++-- packages/edit-widgets/src/blocks/legacy-widget/editor.scss | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/edit-widgets/src/blocks/legacy-widget/edit/handler.js b/packages/edit-widgets/src/blocks/legacy-widget/edit/handler.js index e4fb1843ecca0..af0822bd54c4f 100644 --- a/packages/edit-widgets/src/blocks/legacy-widget/edit/handler.js +++ b/packages/edit-widgets/src/blocks/legacy-widget/edit/handler.js @@ -96,9 +96,9 @@ class LegacyWidgetEditHandler extends Component { { ...componentProps } > { title && ( -
+

{ title } -

+ ) }
Date: Tue, 29 Sep 2020 12:19:12 +0300 Subject: [PATCH 39/79] Show PostFeaturedImage in editor (#25412) * show PostFeaturedImage in editor * change wording * add placeholder chip --- packages/block-library/src/editor.scss | 1 + .../src/post-featured-image/block.json | 3 +- .../src/post-featured-image/edit.js | 35 ++++++++++++------- .../src/post-featured-image/editor.scss | 19 ++++++++++ .../src/responsive-wrapper/style.scss | 4 +++ 5 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 packages/block-library/src/post-featured-image/editor.scss diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index ee185fde8bdc9..1f7387b6b89bb 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -51,6 +51,7 @@ @import "./video/editor.scss"; @import "./query-loop/editor.scss"; @import "./query/editor.scss"; +@import "./post-featured-image/editor.scss"; /** * Import styles from internal editor components used by the blocks. diff --git a/packages/block-library/src/post-featured-image/block.json b/packages/block-library/src/post-featured-image/block.json index 106cdc3c76f51..15506f7ce33c4 100644 --- a/packages/block-library/src/post-featured-image/block.json +++ b/packages/block-library/src/post-featured-image/block.json @@ -2,7 +2,8 @@ "name": "core/post-featured-image", "category": "design", "usesContext": [ - "postId" + "postId", + "postType" ], "supports": { "html": false diff --git a/packages/block-library/src/post-featured-image/edit.js b/packages/block-library/src/post-featured-image/edit.js index c244a3b83b934..5ae60aa299985 100644 --- a/packages/block-library/src/post-featured-image/edit.js +++ b/packages/block-library/src/post-featured-image/edit.js @@ -1,35 +1,46 @@ /** * WordPress dependencies */ -import { useEntityProp, useEntityId } from '@wordpress/core-data'; +import { useEntityProp } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; -import { ResponsiveWrapper } from '@wordpress/components'; +import { ResponsiveWrapper, Icon } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { postFeaturedImage as icon } from '@wordpress/icons'; -function PostFeaturedImageDisplay() { +function PostFeaturedImageDisplay( { context: { postId, postType } } ) { const [ featuredImage ] = useEntityProp( 'postType', - 'post', - 'featured_media' + postType, + 'featured_media', + postId ); const media = useSelect( ( select ) => featuredImage && select( 'core' ).getMedia( featuredImage ), [ featuredImage ] ); - return media ? ( + if ( ! media ) { + return ( +
+ +

{ __( 'Featured Image' ) }

+
+ ); + } + const alt = media.alt_text || __( 'No alternative text set' ); + return ( - Post Featured Media + { - ) : null; + ); } -export default function PostFeaturedImageEdit() { - if ( ! useEntityId( 'postType', 'post' ) ) { - return __( 'Post Featured Image' ); +export default function PostFeaturedImageEdit( props ) { + if ( ! props.context?.postId ) { + return __( 'Featured Image' ); } - return ; + return ; } diff --git a/packages/block-library/src/post-featured-image/editor.scss b/packages/block-library/src/post-featured-image/editor.scss new file mode 100644 index 0000000000000..99bc213a4170a --- /dev/null +++ b/packages/block-library/src/post-featured-image/editor.scss @@ -0,0 +1,19 @@ +div[data-type="core/post-featured-image"] { + .post-featured-image_placeholder { + display: flex; + flex-direction: row; + align-items: flex-start; + border-radius: $radius-block-ui; + background-color: $white; + box-shadow: inset 0 0 0 $border-width $gray-900; + padding: $grid-unit-15; + svg { + margin-right: $grid-unit-15; + } + p { + font-family: $default-font; + font-size: $default-font-size; + margin: 0; + } + } +} diff --git a/packages/components/src/responsive-wrapper/style.scss b/packages/components/src/responsive-wrapper/style.scss index b6a556bf3425d..35b65c8637d59 100644 --- a/packages/components/src/responsive-wrapper/style.scss +++ b/packages/components/src/responsive-wrapper/style.scss @@ -5,6 +5,10 @@ & > span { display: block; } + &, + & > img { + width: auto; + } } .components-responsive-wrapper__content { From eba79f121b8455abb302500e25fece3c0031ece2 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Tue, 29 Sep 2020 12:17:38 +0200 Subject: [PATCH 40/79] Echo before/after_widget in block widget render method (#25693) --- lib/class-wp-widget-block.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/class-wp-widget-block.php b/lib/class-wp-widget-block.php index e29797d4271b2..0e1bca7335841 100644 --- a/lib/class-wp-widget-block.php +++ b/lib/class-wp-widget-block.php @@ -53,7 +53,9 @@ public function __construct() { * @global WP_Post $post Global post object. */ public function widget( $args, $instance ) { + echo $args['before_widget']; echo do_blocks( $instance['content'] ); + echo $args['after_widget']; } /** From f8f86075e5c323a0e215545d595c48a6c3c08d37 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Tue, 29 Sep 2020 13:18:33 +0300 Subject: [PATCH 41/79] Update and move some Query filters (#25674) * update/move Query filters * Change max fetched terms limit * get default posts_per_page from options * fix php linting * Update InputControl to align label/control to the edges and have custom widths. * fallback for undefined categoryIds Co-authored-by: Jon Q --- lib/edit-site-page.php | 1 + .../test/__snapshots__/index.js.snap | 2 +- .../src/components/unit-control/README.md | 2 +- packages/block-library/src/query-loop/edit.js | 6 +- packages/block-library/src/query/block.json | 2 +- packages/block-library/src/query/constants.js | 2 + .../block-library/src/query/edit/index.js | 14 ++ .../query/edit/query-inspector-controls.js | 82 ++++++++- .../src/query/edit/query-toolbar.js | 157 +++++------------- .../components/src/input-control/README.md | 2 +- .../components/src/input-control/index.js | 2 + .../src/input-control/input-base.js | 5 + .../styles/input-control-styles.js | 46 +++-- .../components/src/number-control/README.md | 2 +- .../components/src/unit-control/README.md | 2 +- .../fixtures/blocks/core__query.json | 2 +- 16 files changed, 189 insertions(+), 140 deletions(-) diff --git a/lib/edit-site-page.php b/lib/edit-site-page.php index 689e628c6eeae..83089981f5975 100644 --- a/lib/edit-site-page.php +++ b/lib/edit-site-page.php @@ -131,6 +131,7 @@ function gutenberg_edit_site_init( $hook ) { 'isRTL' => is_rtl(), 'maxUploadFileSize' => $max_upload_size, 'siteUrl' => site_url(), + 'postsPerPage' => get_option( 'posts_per_page' ), ); $settings['styles'] = gutenberg_get_editor_styles(); diff --git a/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap index e0233344f6352..08c0a5a88f75b 100644 --- a/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Basic rendering should render with required props 1`] = `"
Padding

Toggle between using the same value for all screen sizes or using a unique value per screen size.

All is used here for testing purposes to ensure we have access to details about the device.

"`; +exports[`Basic rendering should render with required props 1`] = `"
Padding

Toggle between using the same value for all screen sizes or using a unique value per screen size.

All is used here for testing purposes to ensure we have access to details about the device.

"`; diff --git a/packages/block-editor/src/components/unit-control/README.md b/packages/block-editor/src/components/unit-control/README.md index 89e66b7f3a444..4d99d85254dec 100644 --- a/packages/block-editor/src/components/unit-control/README.md +++ b/packages/block-editor/src/components/unit-control/README.md @@ -71,7 +71,7 @@ If this property is added, a label will be generated using label property as the #### labelPosition -The position of the label (`top`, `side`, or `bottom`). +The position of the label (`top`, `side`, `bottom`, or `edge`). - Type: `String` - Required: No diff --git a/packages/block-library/src/query-loop/edit.js b/packages/block-library/src/query-loop/edit.js index 6e2113b496bbf..fbf92894f03cc 100644 --- a/packages/block-library/src/query-loop/edit.js +++ b/packages/block-library/src/query-loop/edit.js @@ -15,7 +15,11 @@ import { */ import { useQueryContext } from '../query'; -const TEMPLATE = [ [ 'core/post-title' ], [ 'core/post-content' ] ]; +const TEMPLATE = [ + [ 'core/post-title' ], + [ 'core/post-date' ], + [ 'core/post-excerpt' ], +]; export default function QueryLoopEdit( { clientId, context: { diff --git a/packages/block-library/src/query/block.json b/packages/block-library/src/query/block.json index 63d489ad0d552..3e6244bc67be9 100644 --- a/packages/block-library/src/query/block.json +++ b/packages/block-library/src/query/block.json @@ -8,7 +8,7 @@ "query": { "type": "object", "default": { - "perPage": 3, + "perPage": null, "pages": 1, "offset": 0, "categoryIds": [], diff --git a/packages/block-library/src/query/constants.js b/packages/block-library/src/query/constants.js index 311232afe3adc..9f606c1c6920c 100644 --- a/packages/block-library/src/query/constants.js +++ b/packages/block-library/src/query/constants.js @@ -1,5 +1,7 @@ export const MAX_FETCHED_TERMS = 100; +export const DEFAULTS_POSTS_PER_PAGE = 3; export default { MAX_FETCHED_TERMS, + DEFAULTS_POSTS_PER_PAGE, }; diff --git a/packages/block-library/src/query/edit/index.js b/packages/block-library/src/query/edit/index.js index e43c16074346d..b0580889d5601 100644 --- a/packages/block-library/src/query/edit/index.js +++ b/packages/block-library/src/query/edit/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { useSelect } from '@wordpress/data'; import { useInstanceId } from '@wordpress/compose'; import { useEffect } from '@wordpress/element'; import { @@ -15,6 +16,7 @@ import { import QueryToolbar from './query-toolbar'; import QueryProvider from './query-provider'; import QueryInspectorControls from './query-inspector-controls'; +import { DEFAULTS_POSTS_PER_PAGE } from '../constants'; const TEMPLATE = [ [ 'core/query-loop' ], [ 'core/query-pagination' ] ]; export default function QueryEdit( { @@ -23,6 +25,18 @@ export default function QueryEdit( { } ) { const instanceId = useInstanceId( QueryEdit ); const blockWrapperProps = useBlockWrapperProps(); + const { postsPerPage } = useSelect( ( select ) => { + const { getSettings } = select( 'core/block-editor' ); + return { + postsPerPage: + +getSettings().postsPerPage || DEFAULTS_POSTS_PER_PAGE, + }; + }, [] ); + useEffect( () => { + if ( ! query.perPage && postsPerPage ) { + updateQuery( { perPage: postsPerPage } ); + } + }, [ query.perPage ] ); // We need this for multi-query block pagination. // Query parameters for each block are scoped to their ID. useEffect( () => { diff --git a/packages/block-library/src/query/edit/query-inspector-controls.js b/packages/block-library/src/query/edit/query-inspector-controls.js index ec6a28d672189..632a80ce4a4c2 100644 --- a/packages/block-library/src/query/edit/query-inspector-controls.js +++ b/packages/block-library/src/query/edit/query-inspector-controls.js @@ -1,19 +1,68 @@ +/** + * External dependencies + */ +import { debounce } from 'lodash'; + /** * WordPress dependencies */ -import { PanelBody, QueryControls } from '@wordpress/components'; + +import { + PanelBody, + QueryControls, + TextControl, + FormTokenField, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; import { useSelect } from '@wordpress/data'; +import { useEffect, useState, useCallback } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { getTermsInfo } from '../utils'; +import { MAX_FETCHED_TERMS } from '../constants'; export default function QueryInspectorControls( { query, setQuery } ) { const { order, orderBy, author: selectedAuthorId } = query; - const { authorList } = useSelect( ( select ) => { + const { authorList, categories, tags } = useSelect( ( select ) => { const { getEntityRecords } = select( 'core' ); + const termsQuery = { per_page: MAX_FETCHED_TERMS }; + const _categories = getEntityRecords( + 'taxonomy', + 'category', + termsQuery + ); + const _tags = getEntityRecords( 'taxonomy', 'post_tag', termsQuery ); return { + categories: getTermsInfo( _categories ), + tags: getTermsInfo( _tags ), authorList: getEntityRecords( 'root', 'user', { per_page: -1 } ), }; }, [] ); + + // Handles categories and tags changes. + const onTermsChange = ( terms, queryProperty ) => ( newTermValues ) => { + const termIds = newTermValues.reduce( ( accumulator, termValue ) => { + const termId = termValue?.id || terms.mapByName[ termValue ]?.id; + if ( termId ) accumulator.push( termId ); + return accumulator; + }, [] ); + setQuery( { [ queryProperty ]: termIds } ); + }; + const onCategoriesChange = onTermsChange( categories, 'categoryIds' ); + const onTagsChange = onTermsChange( tags, 'tagIds' ); + + const [ querySearch, setQuerySearch ] = useState( query.search ); + const onChangeDebounced = useCallback( + debounce( () => setQuery( { search: querySearch } ), 250 ), + [ querySearch ] + ); + useEffect( () => { + onChangeDebounced(); + return onChangeDebounced.cancel; + }, [ querySearch, onChangeDebounced ] ); return ( @@ -29,6 +78,35 @@ export default function QueryInspectorControls( { query, setQuery } ) { } ) } /> + { categories?.terms?.length > 0 && ( + ( { + id: categoryId, + value: categories.mapById[ categoryId ].name, + } ) + ) } + suggestions={ categories.names } + onChange={ onCategoriesChange } + /> + ) } + { tags?.terms?.length > 0 && ( + ( { + id: tagId, + value: tags.mapById[ tagId ].name, + } ) ) } + suggestions={ tags.names } + onChange={ onTagsChange } + /> + ) } + ); diff --git a/packages/block-library/src/query/edit/query-toolbar.js b/packages/block-library/src/query/edit/query-toolbar.js index 3436348975e24..8d519befd697c 100644 --- a/packages/block-library/src/query/edit/query-toolbar.js +++ b/packages/block-library/src/query/edit/query-toolbar.js @@ -1,70 +1,22 @@ -/** - * External dependencies - */ -import { debounce } from 'lodash'; - /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; -import { useEffect, useState, useCallback } from '@wordpress/element'; import { - Toolbar, + ToolbarGroup, Dropdown, ToolbarButton, RangeControl, - TextControl, - FormTokenField, + BaseControl, + __experimentalNumberControl as NumberControl, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { postList } from '@wordpress/icons'; -/** - * Internal dependencies - */ -import { getTermsInfo } from '../utils'; -import { MAX_FETCHED_TERMS } from '../constants'; - export default function QueryToolbar( { query, setQuery } ) { - const { categories, tags } = useSelect( ( select ) => { - const { getEntityRecords } = select( 'core' ); - const termsQuery = { per_page: MAX_FETCHED_TERMS }; - const _categories = getEntityRecords( - 'taxonomy', - 'category', - termsQuery - ); - const _tags = getEntityRecords( 'taxonomy', 'post_tag', termsQuery ); - return { - categories: getTermsInfo( _categories ), - tags: getTermsInfo( _tags ), - }; - }, [] ); - const [ querySearch, setQuerySearch ] = useState( query.search ); - const onChangeDebounced = useCallback( - debounce( () => setQuery( { search: querySearch } ), 250 ), - [ querySearch ] - ); - useEffect( () => { - onChangeDebounced(); - return onChangeDebounced.cancel; - }, [ querySearch, onChangeDebounced ] ); - - // Handles categories and tags changes. - const onTermsChange = ( terms, queryProperty ) => ( newTermValues ) => { - const termIds = newTermValues.reduce( ( accumulator, termValue ) => { - const termId = termValue?.id || terms.mapByName[ termValue ]?.id; - if ( termId ) accumulator.push( termId ); - return accumulator; - }, [] ); - setQuery( { [ queryProperty ]: termIds } ); - }; - const onCategoriesChange = onTermsChange( categories, 'categoryIds' ); - const onTagsChange = onTermsChange( tags, 'tagIds' ); - return ( - + ( ( <> - - setQuery( { perPage: value ?? -1 } ) - } - /> - - setQuery( { pages: value ?? -1 } ) - } - /> - - setQuery( { offset: value ?? 0 } ) - } - /> - { categories?.terms && ( - ( { - id: categoryId, - value: - categories.mapById[ categoryId ] - .name, - } ) - ) } - suggestions={ categories.names } - onChange={ onCategoriesChange } + + + setQuery( { perPage: +value ?? -1 } ) + } + step="1" + value={ query.perPage } + isDragEnabled={ false } + /> + + + + setQuery( { offset: +value } ) + } + step="1" + value={ query.offset } + isDragEnabled={ false } /> - ) } - { tags?.terms && ( - ( { - id: tagId, - value: tags.mapById[ tagId ].name, - } ) - ) } - suggestions={ tags.names } - onChange={ onTagsChange } + + + + setQuery( { pages: value ?? -1 } ) + } /> - ) } - setQuerySearch( value ) } - /> + ) } /> - + ); } diff --git a/packages/components/src/input-control/README.md b/packages/components/src/input-control/README.md index 6af3626a09f1d..fef516a7a3f18 100644 --- a/packages/components/src/input-control/README.md +++ b/packages/components/src/input-control/README.md @@ -54,7 +54,7 @@ If this property is added, a label will be generated using label property as the ### labelPosition -The position of the label (`top`, `side`, or `bottom`). +The position of the label (`top`, `side`, `bottom`, or `edge`). - Type: `String` - Required: No diff --git a/packages/components/src/input-control/index.js b/packages/components/src/input-control/index.js index e56e86e8a384b..56eb23d950f70 100644 --- a/packages/components/src/input-control/index.js +++ b/packages/components/src/input-control/index.js @@ -26,6 +26,7 @@ function useUniqueId( idProp ) { export function InputControl( { __unstableStateReducer: stateReducer = ( state ) => state, + __unstableInputWidth, className, disabled = false, hideLabelFromVision = false, @@ -51,6 +52,7 @@ export function InputControl( return ( @@ -60,9 +63,11 @@ export function InputBase( { prefix && ( diff --git a/packages/components/src/input-control/styles/input-control-styles.js b/packages/components/src/input-control/styles/input-control-styles.js index cd57f1ef4ca30..ca48b2d32d7ac 100644 --- a/packages/components/src/input-control/styles/input-control-styles.js +++ b/packages/components/src/input-control/styles/input-control-styles.js @@ -33,6 +33,10 @@ const rootLabelPositionStyles = ( { labelPosition } ) => { align-items: flex-start; flex-direction: column-reverse; `; + case 'edge': + return css` + justify-content: space-between; + `; default: return ''; } @@ -42,9 +46,9 @@ export const Root = styled( Flex )` position: relative; border-radius: 2px; - ${ rootFloatLabelStyles }; - ${ rootFocusedStyles }; - ${ rootLabelPositionStyles }; + ${ rootFloatLabelStyles } + ${ rootFocusedStyles } + ${ rootLabelPositionStyles } `; const containerDisabledStyles = ( { disabled } ) => { @@ -55,12 +59,18 @@ const containerDisabledStyles = ( { disabled } ) => { return css( { backgroundColor } ); }; -const containerWidthStyles = ( { labelPosition } ) => { +const containerWidthStyles = ( { __unstableInputWidth, labelPosition } ) => { + if ( ! __unstableInputWidth ) return css( { width: '100%' } ); + if ( labelPosition === 'side' ) return ''; - return css` - width: 100%; - `; + if ( labelPosition === 'edge' ) { + return css( { + flex: `0 0 ${ __unstableInputWidth }`, + } ); + } + + return css( { width: __unstableInputWidth } ); }; export const Container = styled.div` @@ -71,8 +81,8 @@ export const Container = styled.div` flex: 1; position: relative; - ${ containerDisabledStyles }; - ${ containerWidthStyles }; + ${ containerDisabledStyles } + ${ containerWidthStyles } `; const disabledStyles = ( { disabled } ) => { @@ -156,8 +166,8 @@ const dragStyles = ( { isDragging, dragCursor } ) => { } return css` - ${ defaultArrowStyles }; - ${ activeDragCursorStyles }; + ${ defaultArrowStyles } + ${ activeDragCursorStyles } `; }; @@ -178,12 +188,12 @@ export const Input = styled.input` padding-right: 8px; width: 100%; - ${ dragStyles }; - ${ disabledStyles }; - ${ fontSizeStyles }; - ${ sizeStyles }; + ${ dragStyles } + ${ disabledStyles } + ${ fontSizeStyles } + ${ sizeStyles } - ${ placeholderStyles }; + ${ placeholderStyles } } `; @@ -206,7 +216,7 @@ const BaseLabel = styled( Text )` padding-top: 0; z-index: 1; - ${ labelTruncation }; + ${ labelTruncation } } `; @@ -252,7 +262,7 @@ export const BackdropUI = styled.div` right: 0; top: 0; - ${ backdropFocusedStyles }; + ${ backdropFocusedStyles } ${ rtl( { paddingLeft: 2 } ) } } `; diff --git a/packages/components/src/number-control/README.md b/packages/components/src/number-control/README.md index 36b21f63a40a8..efb77dd18a40a 100644 --- a/packages/components/src/number-control/README.md +++ b/packages/components/src/number-control/README.md @@ -72,7 +72,7 @@ If this property is added, a label will be generated using label property as the ### labelPosition -The position of the label (`top`, `side`, or `bottom`). +The position of the label (`top`, `side`, `bottom`, or `edge`). - Type: `String` - Required: No diff --git a/packages/components/src/unit-control/README.md b/packages/components/src/unit-control/README.md index ad60d0abd445c..b024e6aaf5e5e 100644 --- a/packages/components/src/unit-control/README.md +++ b/packages/components/src/unit-control/README.md @@ -50,7 +50,7 @@ If this property is added, a label will be generated using label property as the ### labelPosition -The position of the label (`top`, `side`, or `bottom`). +The position of the label (`top`, `side`, `bottom`, or `edge`). - Type: `String` - Required: No diff --git a/packages/e2e-tests/fixtures/blocks/core__query.json b/packages/e2e-tests/fixtures/blocks/core__query.json index c225bc2053ea8..3848bc06e77a7 100644 --- a/packages/e2e-tests/fixtures/blocks/core__query.json +++ b/packages/e2e-tests/fixtures/blocks/core__query.json @@ -5,7 +5,7 @@ "isValid": true, "attributes": { "query": { - "perPage": 3, + "perPage": null, "pages": 1, "offset": 0, "categoryIds": [], From a193029e630ba1ff6140481c220ca33fade5d7a5 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Tue, 29 Sep 2020 15:21:06 +0300 Subject: [PATCH 42/79] Remove myself from codeowners (#25696) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2773fa8ea7581..2832c860de002 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -12,7 +12,7 @@ # Blocks /packages/block-library @Soean @ajitbohra @talldan -/packages/block-library/src/gallery @mkevins @pinarol +/packages/block-library/src/gallery @mkevins /packages/block-library/src/social-links @mkaz /packages/block-library/src/social-link @mkaz /packages/block-library/src/image @ajlende From 4c04c6e91da206c4665ea36ab005aa4cb4798c36 Mon Sep 17 00:00:00 2001 From: Jacopo Tomasone Date: Tue, 29 Sep 2020 15:25:02 +0200 Subject: [PATCH 43/79] Site Editor: Update Navigation Panel Toggle UI (#25622) Update the toggle UI and the back navigation of the Site Editor navigation panel. --- .../src/navigation/back-button/index.js | 17 +++--- .../header/navigation-toggle/index.js | 4 +- .../header/navigation-toggle/style.scss | 57 +++++++++++++------ .../left-sidebar/navigation-panel/index.js | 21 ++++--- .../left-sidebar/navigation-panel/style.scss | 27 ++++++++- 5 files changed, 90 insertions(+), 36 deletions(-) diff --git a/packages/components/src/navigation/back-button/index.js b/packages/components/src/navigation/back-button/index.js index 3752dcfa635f7..c4e10ab6f157b 100644 --- a/packages/components/src/navigation/back-button/index.js +++ b/packages/components/src/navigation/back-button/index.js @@ -6,6 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ +import { forwardRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Icon, chevronLeft } from '@wordpress/icons'; @@ -15,13 +16,10 @@ import { Icon, chevronLeft } from '@wordpress/icons'; import { useNavigationContext } from '../context'; import { MenuBackButtonUI } from '../styles/navigation-styles'; -export default function NavigationBackButton( { - backButtonLabel, - className, - href, - onClick, - parentMenu, -} ) { +function NavigationBackButton( + { backButtonLabel, className, href, onClick, parentMenu }, + ref +) { const { setActiveMenu, navigationTree } = useNavigationContext(); const classes = classnames( @@ -34,14 +32,17 @@ export default function NavigationBackButton( { return ( parentMenu ? setActiveMenu( parentMenu, 'right' ) : onClick } + ref={ ref } > { backButtonLabel || parentMenuTitle || __( 'Back' ) } ); } + +export default forwardRef( NavigationBackButton ); diff --git a/packages/edit-site/src/components/header/navigation-toggle/index.js b/packages/edit-site/src/components/header/navigation-toggle/index.js index 8a382823fe550..5910757086611 100644 --- a/packages/edit-site/src/components/header/navigation-toggle/index.js +++ b/packages/edit-site/src/components/header/navigation-toggle/index.js @@ -35,7 +35,7 @@ function NavigationToggle( { icon, isOpen, onClick } ) { return null; } - let buttonIcon = ; + let buttonIcon = ; if ( siteIconUrl ) { buttonIcon = ( @@ -48,7 +48,7 @@ function NavigationToggle( { icon, isOpen, onClick } ) { } else if ( isRequestingSiteIcon ) { buttonIcon = null; } else if ( icon ) { - buttonIcon = ; + buttonIcon = ; } return ( diff --git a/packages/edit-site/src/components/header/navigation-toggle/style.scss b/packages/edit-site/src/components/header/navigation-toggle/style.scss index d09ba381b481e..1cabaef56c0c5 100644 --- a/packages/edit-site/src/components/header/navigation-toggle/style.scss +++ b/packages/edit-site/src/components/header/navigation-toggle/style.scss @@ -2,43 +2,64 @@ display: none; @include break-medium() { - display: flex; align-items: center; - background-color: #1e1e1e; - height: 61px; + background: $gray-900; border-radius: 0; + display: flex; + height: $header-height + $border-width; // Cover header border + width: $header-height; } } .edit-site-navigation-toggle.is-open { + flex-shrink: 0; + height: $header-height + $border-width; // Cover header border width: 300px; + + // Delay to sync with `NavigationPanel` animation + transition: width 80ms linear; + transition-delay: 20ms; + @include reduce-motion("transition"); + + .edit-site-navigation-toggle__button { + background: $gray-900; + } } .edit-site-navigation-toggle__button { - color: #fff; - margin-left: 14px; - margin-right: 14px; + align-items: center; + background: #23282e; // WP-admin gray. + color: $white; + height: $header-height + $border-width; // Cover header border + width: $header-height; + + &.has-icon { + min-width: $header-height; - &:hover { - color: #ddd; + &:hover { + background: #32373d; // WP-admin light-gray. + color: $white; + } + &:active { + color: $white; + } + &:focus { + box-shadow: inset 0 0 0 $border-width-focus var(--wp-admin-theme-color), inset 0 0 0 3px $white; + } } } -.edit-site-navigation-toggle.components-button.has-icon { - justify-content: flex-start; - padding: 0; - height: 32px; - width: 32px; - min-width: 32px; +.edit-site-navigation-toggle__site-icon { + width: 36px; } .edit-site-navigation-toggle__site-title { font-style: normal; font-weight: 600; - font-size: 13px; - line-height: 16px; - color: #ddd; - margin-right: 14px; + font-size: $default-font-size; + line-height: $default-line-height; + color: $gray-300; + margin: 0 $grid-unit-20 0 $grid-unit-10; display: -webkit-box; -webkit-line-clamp: 2; diff --git a/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js b/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js index 9f3e2c5c120ac..2e48ec5e04112 100644 --- a/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js @@ -1,29 +1,36 @@ /** * WordPress dependencies */ -import { useState } from '@wordpress/element'; +import { useEffect, useRef, useState } from '@wordpress/element'; import { __experimentalNavigation as Navigation, + __experimentalNavigationBackButton as NavigationBackButton, __experimentalNavigationGroup as NavigationGroup, __experimentalNavigationItem as NavigationItem, __experimentalNavigationMenu as NavigationMenu, } from '@wordpress/components'; import { getBlockType, getBlockFromExample } from '@wordpress/blocks'; import { BlockPreview } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; const NavigationPanel = () => { const [ showPreview, setShowPreview ] = useState( false ); + const ref = useRef(); + + useEffect( () => { + ref.current.focus(); + }, [ ref ] ); return (
+ - - Date: Tue, 29 Sep 2020 16:13:48 +0200 Subject: [PATCH 44/79] Site Editor: Add template switcher to navigation panel (#25615) --- packages/e2e-tests/experimental-features.js | 11 + .../experiments/multi-entity-editing.test.js | 21 +- .../experiments/multi-entity-saving.test.js | 9 +- .../specs/experiments/template-part.test.js | 13 +- .../edit-site/src/components/header/index.js | 23 -- .../left-sidebar/navigation-panel/index.js | 80 ++++--- .../left-sidebar/navigation-panel/style.scss | 24 ++ .../template-switcher/index.js | 206 +++++++++++++++++ .../template-switcher/style.scss | 45 ++++ .../template-switcher/template-preview.js | 35 +++ .../template-switcher/theme-preview.js | 13 +- .../src/components/template-switcher/index.js | 213 ------------------ .../components/template-switcher/style.scss | 67 ------ .../template-switcher/template-preview.js | 33 --- packages/edit-site/src/style.scss | 2 +- 15 files changed, 398 insertions(+), 397 deletions(-) create mode 100644 packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/index.js create mode 100644 packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/style.scss create mode 100644 packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/template-preview.js rename packages/edit-site/src/components/{ => left-sidebar/navigation-panel}/template-switcher/theme-preview.js (70%) delete mode 100644 packages/edit-site/src/components/template-switcher/index.js delete mode 100644 packages/edit-site/src/components/template-switcher/style.scss delete mode 100644 packages/edit-site/src/components/template-switcher/template-preview.js diff --git a/packages/e2e-tests/experimental-features.js b/packages/e2e-tests/experimental-features.js index 3c33f930012dd..2a270a8431b06 100644 --- a/packages/e2e-tests/experimental-features.js +++ b/packages/e2e-tests/experimental-features.js @@ -37,3 +37,14 @@ export function useExperimentalFeatures( features ) { beforeAll( () => setExperimentalFeaturesState( features, true ) ); afterAll( () => setExperimentalFeaturesState( features, false ) ); } + +export const openNavigation = async () => { + const isOpen = !! ( await page.$( + '.edit-site-navigation-toggle.is-open' + ) ); + + if ( ! isOpen ) { + await page.click( '.edit-site-navigation-toggle__button' ); + await page.waitForSelector( '.edit-site-navigation-panel' ); + } +}; diff --git a/packages/e2e-tests/specs/experiments/multi-entity-editing.test.js b/packages/e2e-tests/specs/experiments/multi-entity-editing.test.js index df8aa129a8e7d..6804e87d8759a 100644 --- a/packages/e2e-tests/specs/experiments/multi-entity-editing.test.js +++ b/packages/e2e-tests/specs/experiments/multi-entity-editing.test.js @@ -18,7 +18,10 @@ import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { useExperimentalFeatures } from '../../experimental-features'; +import { + useExperimentalFeatures, + openNavigation, +} from '../../experimental-features'; const visitSiteEditor = async () => { const query = addQueryArgs( '', { @@ -31,19 +34,11 @@ const visitSiteEditor = async () => { ); }; -const openTemplateDropdown = async () => { - // Open the dropdown menu. - const templateDropdown = - 'button.components-dropdown-menu__toggle[aria-label="Switch Template"]'; - await page.click( templateDropdown ); - await page.waitForSelector( '.edit-site-template-switcher__popover' ); -}; - const getTemplateDropdownElement = async ( itemName ) => { - await openTemplateDropdown(); - const [ item ] = await page.$x( - `//div[contains(@class, "edit-site-template-switcher__popover")]//button[contains(., "${ itemName }")]` - ); + await openNavigation(); + const selector = `//div[contains(@class, "edit-site-navigation-panel")]//button[contains(., "${ itemName }")]`; + await page.waitForXPath( selector ); + const [ item ] = await page.$x( selector ); return item; }; diff --git a/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js b/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js index 43bb2eb7c27e7..af8cb346e19f8 100644 --- a/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js +++ b/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js @@ -13,7 +13,10 @@ import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { useExperimentalFeatures } from '../../experimental-features'; +import { + useExperimentalFeatures, + openNavigation, +} from '../../experimental-features'; describe( 'Multi-entity save flow', () => { // Selectors - usable between Post/Site editors. @@ -222,8 +225,6 @@ describe( 'Multi-entity save flow', () => { const saveSiteSelector = '.edit-site-save-button__button'; const activeSaveSiteSelector = `${ saveSiteSelector }[aria-disabled=false]`; const disabledSaveSiteSelector = `${ saveSiteSelector }[aria-disabled=true]`; - const templateDropdownSelector = - '.components-dropdown-menu__toggle[aria-label="Switch Template"]'; const saveA11ySelector = '.edit-site-editor__toggle-save-panel-button'; it( 'Should be enabled after edits', async () => { @@ -234,7 +235,7 @@ describe( 'Multi-entity save flow', () => { await visitAdminPage( 'admin.php', query ); // Ensure we are on 'front-page' demo template. - await page.click( templateDropdownSelector ); + await openNavigation(); const demoTemplateButton = await page.waitForXPath( demoTemplateSelector ); diff --git a/packages/e2e-tests/specs/experiments/template-part.test.js b/packages/e2e-tests/specs/experiments/template-part.test.js index f18f13e3e8724..dfec46717a562 100644 --- a/packages/e2e-tests/specs/experiments/template-part.test.js +++ b/packages/e2e-tests/specs/experiments/template-part.test.js @@ -13,7 +13,10 @@ import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { useExperimentalFeatures } from '../../experimental-features'; +import { + useExperimentalFeatures, + openNavigation, +} from '../../experimental-features'; describe( 'Template Part', () => { useExperimentalFeatures( [ @@ -43,9 +46,7 @@ describe( 'Template Part', () => { it( 'Should load customizations when in a template even if only the slug and theme attributes are set.', async () => { // Switch to editing the header template part. - await page.click( - '.components-dropdown-menu__toggle[aria-label="Switch Template"]' - ); + await openNavigation(); const switchToHeaderTemplatePartButton = await page.waitForXPath( '//button[contains(text(), "header")]' ); @@ -63,9 +64,7 @@ describe( 'Template Part', () => { ); // Switch back to the front page template. - await page.click( - '.components-dropdown-menu__toggle[aria-label="Switch Template"]' - ); + await openNavigation(); const [ switchToFrontPageTemplateButton ] = await page.$x( '//button[contains(text(), "front-page")]' ); diff --git a/packages/edit-site/src/components/header/index.js b/packages/edit-site/src/components/header/index.js index 9f1b861b190d0..5f74540f02d57 100644 --- a/packages/edit-site/src/components/header/index.js +++ b/packages/edit-site/src/components/header/index.js @@ -22,7 +22,6 @@ import { Button } from '@wordpress/components'; */ import MoreMenu from './more-menu'; import PageSwitcher from '../page-switcher'; -import TemplateSwitcher from '../template-switcher'; import SaveButton from '../save-button'; import UndoButton from './undo-redo/undo'; import RedoButton from './undo-redo/redo'; @@ -40,9 +39,6 @@ export default function Header( { deviceType, hasFixedToolbar, template, - templateId, - templatePartId, - templateType, page, showOnFront, } = useSelect( ( select ) => { @@ -76,10 +72,6 @@ export default function Header( { const { __experimentalSetPreviewDeviceType: setPreviewDeviceType, - setTemplate, - addTemplate, - removeTemplate, - setTemplatePart, setPage, } = useDispatch( 'core/edit-site' ); @@ -123,21 +115,6 @@ export default function Header( { activePage={ page } onActivePageChange={ setPage } /> -
- / -
-
diff --git a/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js b/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js index 2e48ec5e04112..36464bcab8d9e 100644 --- a/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/index.js @@ -1,60 +1,80 @@ /** * WordPress dependencies */ -import { useEffect, useRef, useState } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; import { __experimentalNavigation as Navigation, - __experimentalNavigationBackButton as NavigationBackButton, - __experimentalNavigationGroup as NavigationGroup, - __experimentalNavigationItem as NavigationItem, __experimentalNavigationMenu as NavigationMenu, + __experimentalNavigationBackButton as NavigationBackButton, } from '@wordpress/components'; -import { getBlockType, getBlockFromExample } from '@wordpress/blocks'; -import { BlockPreview } from '@wordpress/block-editor'; +import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import TemplateSwitcher from './template-switcher'; + const NavigationPanel = () => { - const [ showPreview, setShowPreview ] = useState( false ); const ref = useRef(); useEffect( () => { ref.current.focus(); }, [ ref ] ); + const { templateId, templatePartId, templateType, page } = useSelect( + ( select ) => { + const { + getTemplateId, + getTemplatePartId, + getTemplateType, + getPage, + } = select( 'core/edit-site' ); + + return { + templateId: getTemplateId(), + templatePartId: getTemplatePartId(), + templateType: getTemplateType(), + page: getPage(), + }; + }, + [] + ); + + const { + setTemplate, + addTemplate, + removeTemplate, + setTemplatePart, + } = useDispatch( 'core/edit-site' ); + return (
- + + - - setShowPreview( true ) } - onMouseLeave={ () => setShowPreview( false ) } - /> - + - - { showPreview && ( -
- -
- ) }
); }; diff --git a/packages/edit-site/src/components/left-sidebar/navigation-panel/style.scss b/packages/edit-site/src/components/left-sidebar/navigation-panel/style.scss index 5265d5d982514..ae871bbf5b7ab 100644 --- a/packages/edit-site/src/components/left-sidebar/navigation-panel/style.scss +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/style.scss @@ -17,6 +17,29 @@ .components-navigation { height: 100%; } + + .components-navigation__item { + position: relative; + + .edit-site-template-switcher__label-home-icon svg path { + color: inherit; + } + + &.is-active { + .edit-site-template-switcher__label-customized-dot { + background: #fff; + } + } + } + + .edit-site-template-switcher__label-home-icon { + top: 50%; + transform: translateY(-50%); + } + + .edit-site-template-switcher__label-customized-dot { + right: 8px; + } } .edit-site-navigation-panel__back-to-dashboard.components-button.is-tertiary { @@ -44,6 +67,7 @@ position: absolute; top: $grid-unit-15; left: calc(100% + #{$grid-unit-15}); + color: $gray-900; @include break-medium { display: block; diff --git a/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/index.js b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/index.js new file mode 100644 index 0000000000000..570ce7640e438 --- /dev/null +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/index.js @@ -0,0 +1,206 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { + Button, + Tooltip, + __experimentalNavigationGroup as NavigationGroup, + __experimentalNavigationItem as NavigationItem, +} from '@wordpress/components'; +import { Icon, home, plus, undo } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import TemplatePreview from './template-preview'; +import ThemePreview from './theme-preview'; + +const TEMPLATE_OVERRIDES = { + page: ( slug ) => `page-${ slug }`, + category: ( slug ) => `category-${ slug }`, + post: ( slug ) => `single-post-${ slug }`, +}; + +function TemplateNavigationItemWithIcon( { + item, + icon, + iconLabel, + homeId, + template, + title, + ...props +} ) { + if ( ! icon && ! iconLabel && template ) { + if ( template.id === homeId ) { + icon = home; + iconLabel = __( 'Home' ); + } else if ( template.status !== 'auto-draft' ) { + icon = ( + + ); + iconLabel = __( 'Customized' ); + } + } + + return ( + + + + ); +} + +export default function TemplateSwitcher( { + page, + activeId, + onActiveIdChange, + onActiveTemplatePartIdChange, + onAddTemplate, + onRemoveTemplate, +} ) { + const [ hoveredTemplatePartId, setHoveredTemplatePartId ] = useState(); + const [ themePreviewVisible, setThemePreviewVisible ] = useState( false ); + + const onMouseEnterTemplatePart = ( id ) => setHoveredTemplatePartId( id ); + const onMouseLeaveTemplatePart = () => setHoveredTemplatePartId( null ); + + const onMouseEnterTheme = () => setThemePreviewVisible( true ); + const onMouseLeaveTheme = () => setThemePreviewVisible( false ); + + const { currentTheme, template, templateParts, homeId } = useSelect( + ( select ) => { + const { + getCurrentTheme, + getEntityRecord, + getEntityRecords, + } = select( 'core' ); + + const _template = getEntityRecord( + 'postType', + 'wp_template', + activeId + ); + + const { getHomeTemplateId } = select( 'core/edit-site' ); + + return { + currentTheme: getCurrentTheme(), + template: _template, + templateParts: _template + ? getEntityRecords( 'postType', 'wp_template_part', { + resolved: true, + template: _template.slug, + } ) + : null, + homeId: getHomeTemplateId(), + }; + }, + [ activeId ] + ); + + const overwriteSlug = + page && + TEMPLATE_OVERRIDES[ page.type ] && + page.slug && + TEMPLATE_OVERRIDES[ page.type ]( page.slug ); + + const overwriteTemplate = () => + onAddTemplate( { + slug: overwriteSlug, + title: overwriteSlug, + status: 'publish', + content: template.content.raw, + } ); + const revertToParent = () => { + onRemoveTemplate( activeId ); + }; + + return ( + <> + + onActiveIdChange( activeId ) } + /> + + { overwriteSlug && + template && + overwriteSlug !== template.slug && ( + + ) } + + { template && overwriteSlug === template.slug && ( + + ) } + + + + { templateParts?.map( ( templatePart ) => { + const key = `template-part-${ templatePart.id }`; + + return ( + + onActiveTemplatePartIdChange( templatePart.id ) + } + onMouseEnter={ () => + onMouseEnterTemplatePart( templatePart.id ) + } + onMouseLeave={ onMouseLeaveTemplatePart } + /> + ); + } ) } + + { ( ! templateParts || templateParts.length === 0 ) && ( + + ) } + + + + + + + { hoveredTemplatePartId && ( + + ) } + + { currentTheme && themePreviewVisible && ( + + ) } + + ); +} diff --git a/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/style.scss b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/style.scss new file mode 100644 index 0000000000000..2e0a1fb6d1a43 --- /dev/null +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/style.scss @@ -0,0 +1,45 @@ +.edit-site-template-switcher__navigation-item { + position: relative; + + &.is-active { + .edit-site-template-switcher__customized-dot { + background: #fff; + } + } + + .edit-site-template-switcher__navigation-item-icon svg path { + color: inherit; + } +} + +.edit-site-template-switcher__navigation-item-icon { + width: 24px; + height: 24px; + position: absolute; + right: 20px; + top: 50%; + transform: translateY(-50%); +} + +.edit-site-template-switcher__customized-dot { + position: absolute; + right: 8px; + top: 50%; + margin-top: -4px; + width: 8px; + height: 8px; + display: block; + background: var(--wp-admin-theme-color); + border-radius: 50%; +} + +.edit-site-template-switcher__theme-preview-name { + font-weight: 500; + font-size: $big-font-size; +} + +.edit-site-template-switcher__theme-preview-screenshot { + margin-bottom: $grid-unit-15; + margin-top: $grid-unit-15; + max-width: 100%; +} diff --git a/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/template-preview.js b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/template-preview.js new file mode 100644 index 0000000000000..56193a6bef08e --- /dev/null +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/template-preview.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { parse } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; +import { BlockPreview } from '@wordpress/block-editor'; +import { useMemo } from '@wordpress/element'; + +export default function TemplatePreview( { entityId } ) { + const template = useSelect( + ( select ) => + select( 'core' ).getEntityRecord( + 'postType', + 'wp_template_part', + entityId + ), + [ entityId ] + ); + + const templateRawContent = template?.content?.raw || ''; + const blocks = useMemo( + () => ( template ? parse( templateRawContent ) : [] ), + [ templateRawContent ] + ); + + if ( ! blocks || blocks.length === 0 ) { + return null; + } + + return ( +
+ +
+ ); +} diff --git a/packages/edit-site/src/components/template-switcher/theme-preview.js b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/theme-preview.js similarity index 70% rename from packages/edit-site/src/components/template-switcher/theme-preview.js rename to packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/theme-preview.js index 0ef8c2068f84a..23d6c2889dc09 100644 --- a/packages/edit-site/src/components/template-switcher/theme-preview.js +++ b/packages/edit-site/src/components/left-sidebar/navigation-panel/template-switcher/theme-preview.js @@ -8,11 +8,11 @@ import { truncate } from 'lodash'; */ import { __, sprintf } from '@wordpress/i18n'; -function ThemePreview( { +export default function ThemePreview( { theme: { author, description, name, screenshot, version }, } ) { return ( -
+
{ truncate( - /* Not using description.rendered here, as we might contain after an opening HTML tag. */ + // We can't use `description.rendered` here because we are truncating the string + // `description.rendered` might contain HTML tags which doesn't play nicely with truncating + // truncate function might truncate in the middle of an HTML tag so we never + // close the HTML tag we are already in description.raw, { length: 120, @@ -47,5 +50,3 @@ function ThemePreview( {
); } - -export default ThemePreview; diff --git a/packages/edit-site/src/components/template-switcher/index.js b/packages/edit-site/src/components/template-switcher/index.js deleted file mode 100644 index a1537253dbe0b..0000000000000 --- a/packages/edit-site/src/components/template-switcher/index.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { useSelect } from '@wordpress/data'; -import { useState } from '@wordpress/element'; -import { - Tooltip, - DropdownMenu, - MenuGroup, - MenuItemsChoice, - MenuItem, -} from '@wordpress/components'; -import { Icon, home, plus, undo } from '@wordpress/icons'; - -/** - * Internal dependencies - */ -import TemplatePreview from './template-preview'; -import ThemePreview from './theme-preview'; - -const TEMPLATE_OVERRIDES = { - page: ( slug ) => `page-${ slug }`, - category: ( slug ) => `category-${ slug }`, - post: ( slug ) => `single-post-${ slug }`, -}; - -function TemplateLabel( { template, homeId } ) { - return ( - <> - { template.slug }{ ' ' } - { template.id === homeId && ( - -
- -
-
- ) } - { template.status !== 'auto-draft' && ( - - - - ) } - - ); -} - -export default function TemplateSwitcher( { - page, - activeId, - activeTemplatePartId, - isTemplatePart, - onActiveIdChange, - onActiveTemplatePartIdChange, - onAddTemplate, - onRemoveTemplate, -} ) { - const [ hoveredTemplate, setHoveredTemplate ] = useState(); - const [ themePreviewVisible, setThemePreviewVisible ] = useState( false ); - - const onHoverTemplatePart = ( id ) => { - setHoveredTemplate( { id, type: 'template-part' } ); - }; - const onMouseEnterTheme = () => { - setThemePreviewVisible( () => true ); - }; - const onMouseLeaveTheme = () => { - setThemePreviewVisible( () => false ); - }; - - const { currentTheme, template, templateParts, homeId } = useSelect( - ( select ) => { - const { - getCurrentTheme, - getEntityRecord, - getEntityRecords, - } = select( 'core' ); - - const _template = getEntityRecord( - 'postType', - 'wp_template', - activeId - ); - - const { getHomeTemplateId } = select( 'core/edit-site' ); - - return { - currentTheme: getCurrentTheme(), - template: _template, - templateParts: _template - ? getEntityRecords( 'postType', 'wp_template_part', { - resolved: true, - template: _template.slug, - } ) - : null, - homeId: getHomeTemplateId(), - }; - }, - [ activeId ] - ); - - const templateItem = { - label: template ? ( - - ) : ( - __( 'Loading…' ) - ), - value: activeId, - slug: template ? template.slug : __( 'Loading…' ), - content: template?.content, - }; - - const templatePartItems = templateParts?.map( ( templatePart ) => ( { - label: , - value: templatePart.id, - slug: templatePart.slug, - } ) ); - - const overwriteSlug = - page && - TEMPLATE_OVERRIDES[ page.type ] && - page.slug && - TEMPLATE_OVERRIDES[ page.type ]( page.slug ); - - const overwriteTemplate = () => - onAddTemplate( { - slug: overwriteSlug, - title: overwriteSlug, - status: 'publish', - content: templateItem.content.raw, - } ); - const revertToParent = async () => { - onRemoveTemplate( activeId ); - }; - return ( - <> - - choice.value === - ( isTemplatePart ? activeTemplatePartId : activeId ) - ).slug, - } } - > - { () => ( - <> - - onActiveIdChange( activeId ) } - > - { templateItem.label } - - { overwriteSlug && - overwriteSlug !== templateItem.slug && ( - - { __( 'Overwrite Template' ) } - - ) } - { overwriteSlug === templateItem.slug && ( - - { __( 'Revert to Parent' ) } - - ) } - - - - - - - { currentTheme.name.raw } - - - { !! hoveredTemplate?.id && ( - - ) } - { themePreviewVisible && ( - - ) } -
- - ) } - - - ); -} diff --git a/packages/edit-site/src/components/template-switcher/style.scss b/packages/edit-site/src/components/template-switcher/style.scss deleted file mode 100644 index fef006af3f157..0000000000000 --- a/packages/edit-site/src/components/template-switcher/style.scss +++ /dev/null @@ -1,67 +0,0 @@ -.edit-site-template-switcher__popover .components-popover__content { - overflow: visible; -} - -.edit-site-template-switcher__popover .components-menu-item__button { - position: relative; -} - -.edit-site-template-switcher__label-home-icon { - width: 24px; - height: 24px; - position: absolute; - right: 20px; -} - -.edit-site-template-switcher__label-customized-dot { - position: absolute; - right: 4px; - top: 50%; - margin-top: -4px; - width: 8px; - height: 8px; - display: block; - background: var(--wp-admin-theme-color); - border-radius: 50%; -} - -.edit-site-template-switcher__label-customized-icon-icon { - width: 100%; -} - -/* - * This doesn't contain anything but it's needed because of dropdown jumpiness - * when there's a div after the last menu group. - */ -.edit-site-template-switcher__footer { - margin-bottom: -$grid-unit-15; -} - -.edit-site-template-switcher__template-preview, -.edit-site-template-switcher__theme-preview { - display: none; - border: $border-width solid $gray-400; - width: 300px; - padding: $grid-unit-20; - background: $white; - box-shadow: $shadow-popover; - border-radius: $radius-block-ui; - position: absolute; - top: -$border-width; - left: calc(100% + #{$grid-unit-15}); - - @include break-medium { - display: block; - } -} - -.edit-site-template-switcher__theme-preview-name { - font-weight: 500; - font-size: $big-font-size; -} - -.edit-site-template-switcher__theme-preview-screenshot { - margin-bottom: $grid-unit-15; - margin-top: $grid-unit-15; - max-width: 100%; -} diff --git a/packages/edit-site/src/components/template-switcher/template-preview.js b/packages/edit-site/src/components/template-switcher/template-preview.js deleted file mode 100644 index c53b4ec1c0795..0000000000000 --- a/packages/edit-site/src/components/template-switcher/template-preview.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * WordPress dependencies - */ -import { parse } from '@wordpress/blocks'; -import { useSelect } from '@wordpress/data'; -import { BlockPreview } from '@wordpress/block-editor'; -import { useMemo } from '@wordpress/element'; - -function TemplatePreview( { item } ) { - const template = useSelect( - ( select ) => { - return select( 'core' ).getEntityRecord( - 'postType', - item.type === 'template' ? 'wp_template' : 'wp_template_part', - item.id - ); - }, - [ item ] - ); - const blocks = useMemo( - () => ( template ? parse( template?.content?.raw || '' ) : [] ), - [ template ] - ); - return ( -
- { !! blocks && ( - - ) } -
- ); -} - -export default TemplatePreview; diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index a503424588b91..86c502bbe7360 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -8,10 +8,10 @@ @import "./components/header/navigation-toggle/style.scss"; @import "./components/left-sidebar/inserter-panel/style.scss"; @import "./components/left-sidebar/navigation-panel/style.scss"; +@import "./components/left-sidebar/navigation-panel/template-switcher/style.scss"; @import "./components/notices/style.scss"; @import "./components/page-switcher/style.scss"; @import "./components/sidebar/style.scss"; -@import "./components/template-switcher/style.scss"; @import "./components/editor/style.scss"; // In order to use mix-blend-mode, this element needs to have an explicitly set background-color. From 363df9f92a94718444ff335582a98220589c522d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Tue, 29 Sep 2020 17:32:12 +0300 Subject: [PATCH 45/79] RichText: remove native props for web (#25700) * Fix passing native props * Fix comment --- .../src/components/rich-text/index.js | 35 +++++++++++-------- packages/rich-text/src/component/index.js | 9 ++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 2e007b629da62..faf3f79140ab8 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -106,7 +106,6 @@ function RichTextWrapper( multiline, inlineToolbar, wrapperClassName, - className, autocompleters, onReplace, placeholder, @@ -120,10 +119,6 @@ function RichTextWrapper( __unstableOnSplitAtEnd: onSplitAtEnd, __unstableOnSplitMiddle: onSplitMiddle, identifier, - // To do: find a better way to implicitly inherit props. - start: startAttr, - reversed, - style, preserveWhiteSpace, __unstableEmbedURLOnPaste, __unstableDisableFormats: disableFormats, @@ -557,9 +552,6 @@ function RichTextWrapper( selectionEnd={ selectionEnd } onSelectionChange={ onSelectionChange } tagName={ tagName } - className={ classnames( classes, className, { - 'keep-placeholder-on-focus': keepPlaceholderOnFocus, - } ) } placeholder={ placeholder } allowedFormats={ adjustedAllowedFormats } withoutInteractiveFormatting={ withoutInteractiveFormatting } @@ -577,11 +569,8 @@ function RichTextWrapper( __unstableDidAutomaticChange={ didAutomaticChange } __unstableUndo={ undo } __unstableDisableFormats={ disableFormats } - style={ style } preserveWhiteSpace={ preserveWhiteSpace } disabled={ disabled } - start={ startAttr } - reversed={ reversed } unstableOnFocus={ unstableOnFocus } __unstableAllowPrefixTransformations={ __unstableAllowPrefixTransformations @@ -611,9 +600,11 @@ function RichTextWrapper( maxWidth={ maxWidth } onBlur={ onBlur } setRef={ setRef } - // Destructuring the id prop before { ...props } doesn't work - // correctly on web https://github.com/WordPress/gutenberg/pull/25624 + // Props to be set on the editable container are destructured on the + // element itself for web (see below), but passed through rich text + // for native. id={ props.id } + style={ props.style } > { ( { isSelected: nestedIsSelected, @@ -643,13 +634,27 @@ function RichTextWrapper( { onKeyDown( event ); editableProps.onKeyDown( event ); diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 816c2d167d3cf..04fcb77f92b60 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import classnames from 'classnames'; import { find, isNil } from 'lodash'; /** @@ -139,8 +138,6 @@ function RichText( children, allowedFormats, withoutInteractiveFormatting, - style, - className, placeholder, disabled, preserveWhiteSpace, @@ -168,7 +165,6 @@ function RichText( __unstableOnExitFormattedText: onExitFormattedText, __unstableOnCreateUndoLevel: onCreateUndoLevel, __unstableIsSelected: isSelected, - ...remainingProps }, ref ) { @@ -1103,10 +1099,9 @@ function RichText( role: 'textbox', 'aria-multiline': true, 'aria-label': placeholder, - ...remainingProps, ref, - style: style ? { ...style, whiteSpace } : defaultStyle, - className: classnames( 'rich-text', className ), + style: defaultStyle, + className: 'rich-text', onPaste: handlePaste, onInput: handleInput, onCompositionStart: handleCompositionStart, From 1719dc261a8088c62d5411b827d64aef15373279 Mon Sep 17 00:00:00 2001 From: Zebulan Stanphill Date: Tue, 29 Sep 2020 09:40:35 -0500 Subject: [PATCH 46/79] Block Editor: use optional chaining and nullish coalescing instead of _.get. (#23632) --- .../src/components/block-icon/index.js | 3 +-- .../src/components/block-icon/index.native.js | 3 +-- .../src/components/colors/with-colors.js | 6 ++---- .../components/default-style-picker/index.js | 17 +++------------ .../use-simulated-media-query/index.js | 21 ++++++++----------- packages/block-editor/src/hooks/align.js | 8 ++----- packages/block-editor/src/store/actions.js | 12 +++++------ packages/block-editor/src/store/reducer.js | 7 +------ packages/block-editor/src/store/selectors.js | 13 +++--------- 9 files changed, 27 insertions(+), 63 deletions(-) diff --git a/packages/block-editor/src/components/block-icon/index.js b/packages/block-editor/src/components/block-icon/index.js index 5c43846229ad2..f605e7f375d5d 100644 --- a/packages/block-editor/src/components/block-icon/index.js +++ b/packages/block-editor/src/components/block-icon/index.js @@ -2,7 +2,6 @@ * External dependencies */ import classnames from 'classnames'; -import { get } from 'lodash'; /** * WordPress dependencies @@ -11,7 +10,7 @@ import { Icon } from '@wordpress/components'; import { blockDefault } from '@wordpress/icons'; export default function BlockIcon( { icon, showColors = false, className } ) { - if ( get( icon, [ 'src' ] ) === 'block-default' ) { + if ( icon?.src === 'block-default' ) { icon = { src: blockDefault, }; 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 fcb66f8f3d1e3..f0845e6b74f8a 100644 --- a/packages/block-editor/src/components/block-icon/index.native.js +++ b/packages/block-editor/src/components/block-icon/index.native.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import { get } from 'lodash'; import { View } from 'react-native'; /** @@ -11,7 +10,7 @@ import { Icon } from '@wordpress/components'; import { blockDefault } from '@wordpress/icons'; export default function BlockIcon( { icon, showColors = false } ) { - if ( get( icon, [ 'src' ] ) === 'block-default' ) { + if ( icon?.src === 'block-default' ) { icon = { src: blockDefault, }; diff --git a/packages/block-editor/src/components/colors/with-colors.js b/packages/block-editor/src/components/colors/with-colors.js index b152047fa94b9..324c31b72a481 100644 --- a/packages/block-editor/src/components/colors/with-colors.js +++ b/packages/block-editor/src/components/colors/with-colors.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, isString, kebabCase, reduce, upperFirst } from 'lodash'; +import { isString, kebabCase, reduce, upperFirst } from 'lodash'; /** * WordPress dependencies @@ -161,9 +161,7 @@ function createColorHOC( colorTypes, withColorPalette ) { const previousColorObject = previousState[ colorAttributeName ]; - const previousColor = get( previousColorObject, [ - 'color', - ] ); + const previousColor = previousColorObject?.color; /** * The "and previousColorObject" condition checks that a previous color object was already computed. * At the start previousColorObject and colorValue are both equal to undefined diff --git a/packages/block-editor/src/components/default-style-picker/index.js b/packages/block-editor/src/components/default-style-picker/index.js index 5880d8bd5850b..5dc15cc4a9e6b 100644 --- a/packages/block-editor/src/components/default-style-picker/index.js +++ b/packages/block-editor/src/components/default-style-picker/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { get } from 'lodash'; - /** * WordPress dependencies */ @@ -22,15 +17,9 @@ export default function DefaultStylePicker( { blockName } ) { const preferredStyleVariations = settings.__experimentalPreferredStyleVariations; return { - preferredStyle: get( preferredStyleVariations, [ - 'value', - blockName, - ] ), - onUpdatePreferredStyleVariations: get( - preferredStyleVariations, - [ 'onChange' ], - null - ), + preferredStyle: preferredStyleVariations?.value?.[ blockName ], + onUpdatePreferredStyleVariations: + preferredStyleVariations?.onChange ?? null, styles: select( 'core/blocks' ).getBlockStyles( blockName ), }; }, diff --git a/packages/block-editor/src/components/use-simulated-media-query/index.js b/packages/block-editor/src/components/use-simulated-media-query/index.js index 52ab33e24f8bf..b5ef9a2a519fe 100644 --- a/packages/block-editor/src/components/use-simulated-media-query/index.js +++ b/packages/block-editor/src/components/use-simulated-media-query/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { filter, get } from 'lodash'; +import { filter } from 'lodash'; import { match } from 'css-mediaquery'; /** @@ -20,18 +20,15 @@ function getStyleSheetsThatMatchHostname() { return []; } - return filter( - get( window, [ 'document', 'styleSheets' ], [] ), - ( styleSheet ) => { - if ( ! styleSheet.href ) { - return false; - } - return ( - getProtocol( styleSheet.href ) === window.location.protocol && - getAuthority( styleSheet.href ) === window.location.host - ); + return filter( window?.document?.styleSheets ?? [], ( styleSheet ) => { + if ( ! styleSheet.href ) { + return false; } - ); + return ( + getProtocol( styleSheet.href ) === window.location.protocol && + getAuthority( styleSheet.href ) === window.location.host + ); + } ); } function isReplaceableMediaRule( rule ) { diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js index f46b7fc029bc2..9d4e66d6556c0 100644 --- a/packages/block-editor/src/hooks/align.js +++ b/packages/block-editor/src/hooks/align.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { get, has, without } from 'lodash'; +import { has, without } from 'lodash'; /** * WordPress dependencies @@ -135,11 +135,7 @@ export const withToolbarControls = createHigherOrderComponent( const updateAlignment = ( nextAlign ) => { if ( ! nextAlign ) { const blockType = getBlockType( props.name ); - const blockDefaultAlign = get( blockType, [ - 'attributes', - 'align', - 'default', - ] ); + const blockDefaultAlign = blockType.attributes?.align?.default; if ( blockDefaultAlign ) { nextAlign = ''; } diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 9465f5b764d0f..cd1365f38fd0c 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { castArray, first, get, last, some } from 'lodash'; +import { castArray, first, last, some } from 'lodash'; /** * WordPress dependencies @@ -252,11 +252,9 @@ export function toggleSelection( isSelectionEnabled = true ) { } function getBlocksWithDefaultStylesApplied( blocks, blockEditorSettings ) { - const preferredStyleVariations = get( - blockEditorSettings, - [ '__experimentalPreferredStyleVariations', 'value' ], - {} - ); + const preferredStyleVariations = + blockEditorSettings?.__experimentalPreferredStyleVariations?.value ?? + {}; return blocks.map( ( block ) => { const blockName = block.name; if ( ! hasBlockSupport( blockName, 'defaultStylePicker', true ) ) { @@ -265,7 +263,7 @@ function getBlocksWithDefaultStylesApplied( blocks, blockEditorSettings ) { if ( ! preferredStyleVariations[ blockName ] ) { return block; } - const className = get( block, [ 'attributes', 'className' ] ); + const className = block.attributes?.className; if ( className?.includes( 'is-style-' ) ) { return block; } diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 058f0794ab2d1..85898175da7da 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -12,7 +12,6 @@ import { keys, isEqual, isEmpty, - get, identity, difference, omitBy, @@ -411,11 +410,7 @@ function withPersistentBlockChange( reducer ) { markNextChangeAsNotPersistent = action.type === 'MARK_NEXT_CHANGE_AS_NOT_PERSISTENT'; - const nextIsPersistentChange = get( - state, - [ 'isPersistentChange' ], - true - ); + const nextIsPersistentChange = state?.isPersistentChange ?? true; if ( state.isPersistentChange === nextIsPersistentChange ) { return state; } diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 85c4a17d94bd4..b84a7ff24aed7 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -5,7 +5,6 @@ import { castArray, flatMap, first, - get, isArray, isBoolean, last, @@ -1273,9 +1272,7 @@ const canInsertBlockTypeUnmemoized = ( return false; } - const parentAllowedBlocks = get( parentBlockListSettings, [ - 'allowedBlocks', - ] ); + const parentAllowedBlocks = parentBlockListSettings?.allowedBlocks; const hasParentAllowedBlock = checkAllowList( parentAllowedBlocks, blockName @@ -1345,7 +1342,7 @@ export function canInsertBlocks( state, clientIds, rootClientId = null ) { * the number of inserts that have occurred. */ function getInsertUsage( state, id ) { - return get( state.preferences.insertUsage, [ id ], null ); + return state.preferences.insertUsage?.[ id ] ?? null; } /** @@ -1735,11 +1732,7 @@ export function __experimentalGetLastBlockAttributeChanges( state ) { * @return {Array} Reusable blocks */ function getReusableBlocks( state ) { - return get( - state, - [ 'settings', '__experimentalReusableBlocks' ], - EMPTY_ARRAY - ); + return state?.settings?.__experimentalReusableBlocks ?? EMPTY_ARRAY; } /** From 290666bcc19cc1d01a4e00d95efabaf559a56087 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad Date: Tue, 29 Sep 2020 11:00:53 -0400 Subject: [PATCH 47/79] Remove the right margin for the right-most list items in the lastest posts block. (#25688) --- packages/block-library/src/latest-posts/style.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/latest-posts/style.scss b/packages/block-library/src/latest-posts/style.scss index e3a34c1387529..cee628dbb20d3 100644 --- a/packages/block-library/src/latest-posts/style.scss +++ b/packages/block-library/src/latest-posts/style.scss @@ -28,7 +28,11 @@ @include break-small { @for $i from 2 through 6 { &.columns-#{ $i } li { - width: calc((100% / #{ $i }) - 1.25em); + width: calc((100% / #{ $i }) - 1.25em + (1.25em / #{ $i })); + + &:nth-child( #{ $i }n ) { + margin-right: 0; + } } } } From 1cad0e16c1fd8e46a57c861e69a86a4d3ee8bbd8 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Tue, 29 Sep 2020 18:41:01 +0300 Subject: [PATCH 48/79] Use `UnitControl` instead of `RangeControl` for column width (#24711) Co-authored-by: Nik Tsekouras Co-authored-by: Jon Q --- .../test/__snapshots__/index.js.snap | 2 +- packages/block-library/src/column/block.json | 4 +- .../block-library/src/column/deprecated.js | 50 +++++++++++++++++++ packages/block-library/src/column/edit.js | 31 +++++++----- packages/block-library/src/column/index.js | 2 + packages/block-library/src/column/save.js | 4 +- .../block-library/src/columns/variations.js | 14 +++--- .../src/input-control/input-base.js | 1 + .../styles/input-control-styles.js | 13 ++++- 9 files changed, 93 insertions(+), 28 deletions(-) create mode 100644 packages/block-library/src/column/deprecated.js diff --git a/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap index 08c0a5a88f75b..c885154f55d76 100644 --- a/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Basic rendering should render with required props 1`] = `"
Padding

Toggle between using the same value for all screen sizes or using a unique value per screen size.

All is used here for testing purposes to ensure we have access to details about the device.

"`; +exports[`Basic rendering should render with required props 1`] = `"
Padding

Toggle between using the same value for all screen sizes or using a unique value per screen size.

All is used here for testing purposes to ensure we have access to details about the device.

"`; diff --git a/packages/block-library/src/column/block.json b/packages/block-library/src/column/block.json index bc8d16aa9601c..0a4f951d1f748 100644 --- a/packages/block-library/src/column/block.json +++ b/packages/block-library/src/column/block.json @@ -9,9 +9,7 @@ "type": "string" }, "width": { - "type": "number", - "min": 0, - "max": 100 + "type": "string" } }, "supports": { diff --git a/packages/block-library/src/column/deprecated.js b/packages/block-library/src/column/deprecated.js new file mode 100644 index 0000000000000..52d3f675df299 --- /dev/null +++ b/packages/block-library/src/column/deprecated.js @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { InnerBlocks } from '@wordpress/block-editor'; + +const deprecated = [ + { + attributes: { + verticalAlignment: { + type: 'string', + }, + width: { + type: 'number', + min: 0, + max: 100, + }, + }, + migrate( attributes ) { + return { + ...attributes, + width: `${ attributes.width }%`, + }; + }, + save( { attributes } ) { + const { verticalAlignment, width } = attributes; + + const wrapperClasses = classnames( { + [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, + } ); + + let style; + if ( Number.isFinite( width ) ) { + style = { flexBasis: width + '%' }; + } + + return ( +
+ +
+ ); + }, + }, +]; + +export default deprecated; diff --git a/packages/block-library/src/column/edit.js b/packages/block-library/src/column/edit.js index 9798a4b398340..a14f37dc13554 100644 --- a/packages/block-library/src/column/edit.js +++ b/packages/block-library/src/column/edit.js @@ -13,7 +13,10 @@ import { InspectorControls, __experimentalUseBlockWrapperProps as useBlockWrapperProps, } from '@wordpress/block-editor'; -import { PanelBody, RangeControl } from '@wordpress/components'; +import { + PanelBody, + __experimentalUnitControl as UnitControl, +} from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -51,10 +54,9 @@ function ColumnEdit( { } ); }; - const hasWidth = Number.isFinite( width ); const blockWrapperProps = useBlockWrapperProps( { className: classes, - style: hasWidth ? { flexBasis: width + '%' } : undefined, + style: width ? { flexBasis: width } : undefined, } ); return ( @@ -67,20 +69,23 @@ function ColumnEdit( { - { + nextWidth = + 0 > parseFloat( nextWidth ) ? '0' : nextWidth; setAttributes( { width: nextWidth } ); } } - min={ 0 } - max={ 100 } - step={ 0.1 } - required - allowReset - placeholder={ - width === undefined ? __( 'Auto' ) : undefined - } + units={ [ + { value: '%', label: '%', default: '' }, + { value: 'px', label: 'px', default: '' }, + { value: 'em', label: 'em', default: '' }, + { value: 'rem', label: 'rem', default: '' }, + { value: 'vw', label: 'vw', default: '' }, + ] } /> diff --git a/packages/block-library/src/column/index.js b/packages/block-library/src/column/index.js index b437c249a82e5..2297974e89fc4 100644 --- a/packages/block-library/src/column/index.js +++ b/packages/block-library/src/column/index.js @@ -7,6 +7,7 @@ import { column as icon } from '@wordpress/icons'; /** * Internal dependencies */ +import deprecated from './deprecated'; import edit from './edit'; import metadata from './block.json'; import save from './save'; @@ -21,4 +22,5 @@ export const settings = { description: __( 'A single column within a columns block.' ), edit, save, + deprecated, }; diff --git a/packages/block-library/src/column/save.js b/packages/block-library/src/column/save.js index d0dda9de3174b..cba4f436c176f 100644 --- a/packages/block-library/src/column/save.js +++ b/packages/block-library/src/column/save.js @@ -16,8 +16,8 @@ export default function save( { attributes } ) { } ); let style; - if ( Number.isFinite( width ) ) { - style = { flexBasis: width + '%' }; + if ( width ) { + style = { flexBasis: width }; } return ( diff --git a/packages/block-library/src/columns/variations.js b/packages/block-library/src/columns/variations.js index 31b58cc71893c..68ce6583ed4b2 100644 --- a/packages/block-library/src/columns/variations.js +++ b/packages/block-library/src/columns/variations.js @@ -53,8 +53,8 @@ const variations = [ ), innerBlocks: [ - [ 'core/column', { width: 33.33 } ], - [ 'core/column', { width: 66.66 } ], + [ 'core/column', { width: '33.33%' } ], + [ 'core/column', { width: '66.66%' } ], ], scope: [ 'block' ], }, @@ -77,8 +77,8 @@ const variations = [ ), innerBlocks: [ - [ 'core/column', { width: 66.66 } ], - [ 'core/column', { width: 33.33 } ], + [ 'core/column', { width: '66.66%' } ], + [ 'core/column', { width: '33.33%' } ], ], scope: [ 'block' ], }, @@ -124,9 +124,9 @@ const variations = [ ), innerBlocks: [ - [ 'core/column', { width: 25 } ], - [ 'core/column', { width: 50 } ], - [ 'core/column', { width: 25 } ], + [ 'core/column', { width: '25%' } ], + [ 'core/column', { width: '50%' } ], + [ 'core/column', { width: '25%' } ], ], scope: [ 'block' ], }, diff --git a/packages/components/src/input-control/input-base.js b/packages/components/src/input-control/input-base.js index 5f0da486f9adb..49ce733912b49 100644 --- a/packages/components/src/input-control/input-base.js +++ b/packages/components/src/input-control/input-base.js @@ -56,6 +56,7 @@ export function InputBase(